diff --git a/Zend/tests/enum/internal_enums.phpt b/Zend/tests/enum/internal_enums.phpt new file mode 100644 index 00000000000..42ea95d2648 --- /dev/null +++ b/Zend/tests/enum/internal_enums.phpt @@ -0,0 +1,58 @@ +--TEST-- +Internal enums +--EXTENSIONS-- +zend_test +--FILE-- +value); +var_dump($bar = ZendTestStringEnum::from("Test2")); +var_dump($bar === ZendTestStringEnum::Bar); +var_dump(ZendTestStringEnum::tryFrom("Test3")); +var_dump(ZendTestStringEnum::cases()); + +var_dump($s = serialize($foo)); +var_dump(unserialize($s)); +var_dump(unserialize($s) === $foo); + +?> +--EXPECT-- +enum(ZendTestUnitEnum::Bar) +bool(true) +bool(true) +enum(ZendTestUnitEnum::Foo) +bool(true) +array(2) { + [0]=> + enum(ZendTestUnitEnum::Foo) + [1]=> + enum(ZendTestUnitEnum::Bar) +} + +enum(ZendTestStringEnum::Foo) +bool(true) +string(5) "Test1" +enum(ZendTestStringEnum::Bar) +bool(true) +NULL +array(2) { + [0]=> + enum(ZendTestStringEnum::Foo) + [1]=> + enum(ZendTestStringEnum::Bar) +} +string(30) "E:22:"ZendTestStringEnum:Foo";" +enum(ZendTestStringEnum::Foo) +bool(true) diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 83a32a4769a..b57daf42b93 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -1298,7 +1298,6 @@ static zend_class_mutable_data *zend_allocate_mutable_data(zend_class_entry *cla { zend_class_mutable_data *mutable_data; - ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE); ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL); ZEND_ASSERT(ZEND_MAP_PTR_GET_IMM(class_type->mutable_data) == NULL); @@ -1331,7 +1330,6 @@ ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_ _zend_hash_append_ptr(constants_table, key, c); } ZEND_HASH_FOREACH_END(); - ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_IMMUTABLE); ZEND_ASSERT(ZEND_MAP_PTR(class_type->mutable_data) != NULL); 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 if (Z_TYPE_P(value) == IS_CONSTANT_AST) { ce->ce_flags &= ~ZEND_ACC_CONSTANTS_UPDATED; ce->ce_flags |= ZEND_ACC_HAS_AST_CONSTANTS; + if (ce->type == ZEND_INTERNAL_CLASS && !ZEND_MAP_PTR(ce->mutable_data)) { + ZEND_MAP_PTR_NEW(ce->mutable_data); + } } if (!zend_hash_add_ptr(&ce->constants_table, name, c)) { diff --git a/Zend/zend_API.h b/Zend/zend_API.h index 2660efee8f8..a51d3f3ad1e 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -416,8 +416,7 @@ ZEND_API zend_result zend_update_class_constants(zend_class_entry *class_type); ZEND_API HashTable *zend_separate_class_constants_table(zend_class_entry *class_type); static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry *ce) { - if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) - && (ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + if ((ce->ce_flags & ZEND_ACC_HAS_AST_CONSTANTS) && ZEND_MAP_PTR(ce->mutable_data)) { zend_class_mutable_data *mutable_data = (zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data); if (mutable_data && mutable_data->constants_table) { @@ -431,8 +430,7 @@ static zend_always_inline HashTable *zend_class_constants_table(zend_class_entry } static zend_always_inline zval *zend_class_default_properties_table(zend_class_entry *ce) { - if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES) - && (ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + if ((ce->ce_flags & ZEND_ACC_HAS_AST_PROPERTIES) && ZEND_MAP_PTR(ce->mutable_data)) { zend_class_mutable_data *mutable_data = (zend_class_mutable_data*)ZEND_MAP_PTR_GET_IMM(ce->mutable_data); return mutable_data->default_properties_table; diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index a829f6255dd..c03813d9e74 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -38,10 +38,6 @@ static inline void *zend_ast_realloc(void *old, size_t old_size, size_t new_size return new; } -static inline size_t zend_ast_size(uint32_t children) { - return sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children; -} - static inline size_t zend_ast_list_size(uint32_t children) { return sizeof(zend_ast_list) - sizeof(zend_ast *) + sizeof(zend_ast *) * children; } diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 08de8246041..86f15358006 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -307,6 +307,10 @@ ZEND_API void ZEND_FASTCALL zend_ast_ref_destroy(zend_ast_ref *ast); typedef void (*zend_ast_apply_func)(zend_ast **ast_ptr, void *context); ZEND_API void zend_ast_apply(zend_ast *ast, zend_ast_apply_func fn, void *context); +static zend_always_inline size_t zend_ast_size(uint32_t children) { + return sizeof(zend_ast) - sizeof(zend_ast *) + sizeof(zend_ast *) * children; +} + static zend_always_inline bool zend_ast_is_special(zend_ast *ast) { return (ast->kind >> ZEND_AST_SPECIAL_SHIFT) & 1; } diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 437fa2f8150..4aa7b75506a 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -371,3 +371,140 @@ void zend_enum_register_props(zend_class_entry *ce) zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &value_default_value, ZEND_ACC_PUBLIC, NULL, value_type); } } + +static const zend_function_entry unit_enum_methods[] = { + ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_FE_END +}; + +static const zend_function_entry backed_enum_methods[] = { + ZEND_NAMED_ME(cases, zend_enum_cases_func, arginfo_class_UnitEnum_cases, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_NAMED_ME(from, zend_enum_from_func, arginfo_class_BackedEnum_from, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_NAMED_ME(tryFrom, zend_enum_try_from_func, arginfo_class_BackedEnum_tryFrom, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + ZEND_FE_END +}; + +ZEND_API zend_class_entry *zend_register_internal_enum( + const char *name, zend_uchar type, zend_function_entry *functions) +{ + ZEND_ASSERT(type == IS_UNDEF || type == IS_LONG || type == IS_STRING); + + zend_class_entry tmp_ce; + INIT_CLASS_ENTRY_EX(tmp_ce, name, strlen(name), functions); + + zend_class_entry *ce = zend_register_internal_class(&tmp_ce); + ce->ce_flags |= ZEND_ACC_ENUM; + ce->enum_backing_type = type; + if (type != IS_UNDEF) { + ce->backed_enum_table = pemalloc(sizeof(HashTable), 1); + zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 1); + } + + zend_enum_register_props(ce); + if (type == IS_UNDEF) { + zend_register_functions( + ce, unit_enum_methods, &ce->function_table, EG(current_module)->type); + zend_class_implements(ce, 1, zend_ce_unit_enum); + } else { + zend_register_functions( + ce, backed_enum_methods, &ce->function_table, EG(current_module)->type); + zend_class_implements(ce, 1, zend_ce_backed_enum); + } + + return ce; +} + +static zend_ast_ref *create_enum_case_ast( + zend_string *class_name, zend_string *case_name, zval *value) { + // TODO: Use custom node type for enum cases? + size_t num_children = value ? 3 : 2; + size_t size = sizeof(zend_ast_ref) + zend_ast_size(num_children) + + num_children * sizeof(zend_ast_zval); + char *p = pemalloc(size, 1); + zend_ast_ref *ref = (zend_ast_ref *) p; p += sizeof(zend_ast_ref); + GC_SET_REFCOUNT(ref, 1); + GC_TYPE_INFO(ref) = GC_CONSTANT_AST | GC_PERSISTENT | GC_IMMUTABLE; + + zend_ast *ast = (zend_ast *) p; p += zend_ast_size(3); + ast->kind = ZEND_AST_CONST_ENUM_INIT; + ast->attr = 0; + ast->lineno = 0; + + ast->child[0] = (zend_ast *) p; p += sizeof(zend_ast_zval); + ast->child[0]->kind = ZEND_AST_ZVAL; + ast->child[0]->attr = 0; + ZEND_ASSERT(ZSTR_IS_INTERNED(class_name)); + ZVAL_STR(zend_ast_get_zval(ast->child[0]), class_name); + + ast->child[1] = (zend_ast *) p; p += sizeof(zend_ast_zval); + ast->child[1]->kind = ZEND_AST_ZVAL; + ast->child[1]->attr = 0; + ZEND_ASSERT(ZSTR_IS_INTERNED(case_name)); + ZVAL_STR(zend_ast_get_zval(ast->child[1]), case_name); + + if (value) { + ast->child[2] = (zend_ast *) p; p += sizeof(zend_ast_zval); + ast->child[2]->kind = ZEND_AST_ZVAL; + ast->child[2]->attr = 0; + ZEND_ASSERT(!Z_REFCOUNTED_P(value)); + ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value); + } else { + ast->child[2] = NULL; + } + + return ref; +} + +ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value) +{ + if (value) { + ZEND_ASSERT(ce->enum_backing_type == Z_TYPE_P(value)); + if (Z_TYPE_P(value) == IS_STRING && !ZSTR_IS_INTERNED(Z_STR_P(value))) { + zval_make_interned_string(value); + } + + zval case_name_zv; + ZVAL_STR(&case_name_zv, case_name); + if (Z_TYPE_P(value) == IS_LONG) { + zend_hash_index_add_new(ce->backed_enum_table, Z_LVAL_P(value), &case_name_zv); + } else { + zend_hash_add_new(ce->backed_enum_table, Z_STR_P(value), &case_name_zv); + } + } else { + ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF); + } + + zval ast_zv; + Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST; + Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value); + zend_class_constant *c = zend_declare_class_constant_ex( + ce, case_name, &ast_zv, ZEND_ACC_PUBLIC, NULL); + ZEND_CLASS_CONST_FLAGS(c) |= ZEND_CLASS_CONST_IS_CASE; +} + +ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value) +{ + zend_string *name_str = zend_string_init_interned(name, strlen(name), 1); + zend_enum_add_case(ce, name_str, value); + zend_string_release(name_str); +} + +ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name) { + zend_class_constant *c = zend_hash_find_ptr(CE_CONSTANTS_TABLE(ce), name); + ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE); + + if (Z_TYPE(c->value) == IS_CONSTANT_AST) { + if (zval_update_constant_ex(&c->value, c->ce) == FAILURE) { + ZEND_UNREACHABLE(); + } + } + ZEND_ASSERT(Z_TYPE(c->value) == IS_OBJECT); + return Z_OBJ(c->value); +} + +ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name) { + zend_string *name_str = zend_string_init(name, strlen(name), 0); + zend_object *result = zend_enum_get_case(ce, name_str); + zend_string_release(name_str); + return result; +} diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h index c0abe7311f6..efabae480f4 100644 --- a/Zend/zend_enum.h +++ b/Zend/zend_enum.h @@ -34,6 +34,13 @@ void zend_verify_enum(zend_class_entry *ce); void zend_enum_register_funcs(zend_class_entry *ce); void zend_enum_register_props(zend_class_entry *ce); +ZEND_API zend_class_entry *zend_register_internal_enum( + const char *name, zend_uchar type, zend_function_entry *functions); +ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value); +ZEND_API void zend_enum_add_case_cstr(zend_class_entry *ce, const char *name, zval *value); +ZEND_API zend_object *zend_enum_get_case(zend_class_entry *ce, zend_string *name); +ZEND_API zend_object *zend_enum_get_case_cstr(zend_class_entry *ce, const char *name); + static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj) { ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM); diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index b253cd79c5c..f9ed19080a6 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -397,6 +397,9 @@ ZEND_API void destroy_zend_class(zval *zv) } break; case ZEND_INTERNAL_CLASS: + if (ce->backed_enum_table) { + zend_hash_release(ce->backed_enum_table); + } if (ce->default_properties_table) { zval *p = ce->default_properties_table; zval *end = p + ce->default_properties_count; @@ -442,7 +445,14 @@ ZEND_API void destroy_zend_class(zval *zv) ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) { if (c->ce == ce) { - zval_internal_ptr_dtor(&c->value); + if (Z_TYPE(c->value) == IS_CONSTANT_AST) { + /* We marked this as IMMUTABLE, but do need to free it when the + * class is destroyed. */ + ZEND_ASSERT(Z_ASTVAL(c->value)->kind == ZEND_AST_CONST_ENUM_INIT); + free(Z_AST(c->value)); + } else { + zval_internal_ptr_dtor(&c->value); + } if (c->doc_comment) { zend_string_release_ex(c->doc_comment, 1); } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 27f9ee538b5..7be9d347885 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -26,6 +26,7 @@ #include "observer.h" #include "fiber.h" #include "zend_attributes.h" +#include "zend_enum.h" #include "Zend/Optimizer/zend_optimizer.h" ZEND_DECLARE_MODULE_GLOBALS(zend_test) @@ -38,6 +39,8 @@ static zend_class_entry *zend_test_attribute; static zend_class_entry *zend_test_ns_foo_class; static zend_class_entry *zend_test_ns2_foo_class; static zend_class_entry *zend_test_ns2_ns_foo_class; +static zend_class_entry *zend_test_unit_enum; +static zend_class_entry *zend_test_string_enum; static zend_object_handlers zend_test_class_handlers; static ZEND_FUNCTION(zend_test_func) @@ -227,6 +230,13 @@ static ZEND_FUNCTION(zend_iterable) ZEND_PARSE_PARAMETERS_END(); } +static ZEND_FUNCTION(zend_get_unit_enum) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_OBJ_COPY(zend_enum_get_case_cstr(zend_test_unit_enum, "Foo")); +} + static ZEND_FUNCTION(namespaced_func) { ZEND_PARSE_PARAMETERS_NONE(); @@ -384,6 +394,17 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_ns2_foo_class = register_class_ZendTestNS2_Foo(); zend_test_ns2_ns_foo_class = register_class_ZendTestNS2_ZendSubNS_Foo(); + zend_test_unit_enum = zend_register_internal_enum("ZendTestUnitEnum", IS_UNDEF, NULL); + zend_enum_add_case_cstr(zend_test_unit_enum, "Foo", NULL); + zend_enum_add_case_cstr(zend_test_unit_enum, "Bar", NULL); + + zval val; + zend_test_string_enum = zend_register_internal_enum("ZendTestStringEnum", IS_STRING, NULL); + ZVAL_PSTRINGL(&val, "Test1", sizeof("Test1")-1); + zend_enum_add_case_cstr(zend_test_string_enum, "Foo", &val); + ZVAL_PSTRINGL(&val, "Test2", sizeof("Test2")-1); + zend_enum_add_case_cstr(zend_test_string_enum, "Bar", &val); + // Loading via dl() not supported with the observer API if (type != MODULE_TEMPORARY) { REGISTER_INI_ENTRIES(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 105f3e7e9dc..a8f8f6a7b68 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -74,6 +74,7 @@ namespace { function zend_iterable(iterable $arg1, ?iterable $arg2 = null): void {} + function zend_get_unit_enum(): ZendTestUnitEnum {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 9d636bafc87..5d6dbd49630 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2a1f8ff8205507259ba19bd379a07b390bc525cd */ + * Stub hash: 93bb8b9120e510e8c3afc29dc0a5d47cb6b5f10e */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -51,6 +51,9 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable, 0, 1, IS_VOID, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_zend_get_unit_enum, 0, 0, ZendTestUnitEnum, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -91,6 +94,7 @@ static ZEND_FUNCTION(zend_string_or_object_or_null); static ZEND_FUNCTION(zend_string_or_stdclass); static ZEND_FUNCTION(zend_string_or_stdclass_or_null); static ZEND_FUNCTION(zend_iterable); +static ZEND_FUNCTION(zend_get_unit_enum); static ZEND_FUNCTION(namespaced_func); static ZEND_METHOD(_ZendTestClass, is_object); static ZEND_METHOD(_ZendTestClass, __toString); @@ -117,6 +121,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_string_or_stdclass, arginfo_zend_string_or_stdclass) ZEND_FE(zend_string_or_stdclass_or_null, arginfo_zend_string_or_stdclass_or_null) ZEND_FE(zend_iterable, arginfo_zend_iterable) + ZEND_FE(zend_get_unit_enum, arginfo_zend_get_unit_enum) ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func) ZEND_FE_END };