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.
This commit is contained in:
Nikita Popov 2021-07-22 15:13:38 +02:00
parent ff8e04ac30
commit a374230c15
11 changed files with 250 additions and 12 deletions

View file

@ -0,0 +1,58 @@
--TEST--
Internal enums
--EXTENSIONS--
zend_test
--FILE--
<?php
var_dump($bar = ZendTestUnitEnum::Bar);
var_dump($bar === ZendTestUnitEnum::Bar);
var_dump($bar instanceof UnitEnum);
var_dump($foo = zend_get_unit_enum());
var_dump($foo === ZendTestUnitEnum::Foo);
var_dump(ZendTestUnitEnum::cases());
echo "\n";
var_dump($foo = ZendTestStringEnum::Foo);
var_dump($foo instanceof BackedEnum);
var_dump(ZendTestStringEnum::Foo->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)

View file

@ -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)) {

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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);

View file

@ -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) {
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);
}

View file

@ -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();

View file

@ -74,6 +74,7 @@ namespace {
function zend_iterable(iterable $arg1, ?iterable $arg2 = null): void {}
function zend_get_unit_enum(): ZendTestUnitEnum {}
}
namespace ZendTestNS {

View file

@ -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
};