Skip to content
Draft
16 changes: 16 additions & 0 deletions Zend/tests/enum/gh20914-001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Bug GH-20914: Internal enums can be cloned
--EXTENSIONS--
zend_test
--FILE--
<?php

try {
var_dump(clone ZendTestIntEnum::Foo);
} catch (Error $e) {
echo $e->getMessage() . "\n";
}

?>
--EXPECT--
Trying to clone an uncloneable object of class ZendTestIntEnum
54 changes: 54 additions & 0 deletions Zend/tests/enum/gh20914-002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
--TEST--
Bug GH-20914: Internal enums can be compared
--EXTENSIONS--
zend_test
--FILE--
<?php

$foo = ZendTestUnitEnum::Foo;
$bar = ZendTestUnitEnum::Bar;

var_dump($foo === $foo);
var_dump($foo == $foo);

var_dump($foo === $bar);
var_dump($foo == $bar);

var_dump($bar === $foo);
var_dump($bar == $foo);

var_dump($foo > $foo);
var_dump($foo < $foo);
var_dump($foo >= $foo);
var_dump($foo <= $foo);

var_dump($foo > $bar);
var_dump($foo < $bar);
var_dump($foo >= $bar);
var_dump($foo <= $bar);

var_dump($foo > true);
var_dump($foo < true);
var_dump($foo >= true);
var_dump($foo <= true);

?>
--EXPECT--
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(true)
bool(true)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(true)
bool(true)
7 changes: 7 additions & 0 deletions Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -2009,6 +2009,13 @@ ZEND_API ZEND_COLD void zend_class_redeclaration_error_ex(int type, zend_string
#define Z_PARAM_OBJ_OF_CLASS_OR_LONG_OR_NULL(dest_obj, _ce, dest_long, is_null) \
Z_PARAM_OBJ_OF_CLASS_OR_LONG_EX(dest_obj, _ce, dest_long, is_null, 1)

#define Z_PARAM_ENUM(dest, _ce) \
{ \
zend_object *_tmp = NULL; \
Z_PARAM_OBJ_OF_CLASS(_tmp, _ce); \
dest = zend_enum_fetch_case_id(_tmp); \
}

/* old "p" */
#define Z_PARAM_PATH_EX(dest, dest_len, check_null, deref) \
Z_PARAM_PROLOGUE(deref, 0); \
Expand Down
9 changes: 6 additions & 3 deletions Zend/zend_ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -995,10 +995,13 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
zend_ast *class_name_ast = ast->child[0];
zend_string *class_name = zend_ast_get_str(class_name_ast);

zend_ast *case_name_ast = ast->child[1];
zend_ast *case_id_ast = ast->child[1];
zend_long case_id = Z_LVAL_P(zend_ast_get_zval(case_id_ast));

zend_ast *case_name_ast = ast->child[2];
zend_string *case_name = zend_ast_get_str(case_name_ast);

zend_ast *case_value_ast = ast->child[2];
zend_ast *case_value_ast = ast->child[3];

zval case_value_zv;
ZVAL_UNDEF(&case_value_zv);
Expand All @@ -1009,7 +1012,7 @@ static zend_result ZEND_FASTCALL zend_ast_evaluate_inner(
}

zend_class_entry *ce = zend_lookup_class(class_name);
zend_enum_new(result, ce, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
zend_enum_new(result, ce, case_id, case_name, case_value_ast != NULL ? &case_value_zv : NULL);
zval_ptr_dtor_nogc(&case_value_zv);
break;
}
Expand Down
6 changes: 3 additions & 3 deletions Zend/zend_ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,15 @@ enum _zend_ast_kind {
ZEND_AST_CONST_ELEM,
ZEND_AST_CLASS_CONST_GROUP,

// Pseudo node for initializing enums
ZEND_AST_CONST_ENUM_INIT,

/* 4 child nodes */
ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT,
ZEND_AST_FOREACH,
ZEND_AST_ENUM_CASE,
ZEND_AST_PROP_ELEM,

// Pseudo node for initializing enums
ZEND_AST_CONST_ENUM_INIT,

/* 5 child nodes */

/* 6 child nodes */
Expand Down
10 changes: 8 additions & 2 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -9565,6 +9565,11 @@ static void zend_compile_enum_case(zend_ast *ast)
ZVAL_STR_COPY(&class_name_zval, enum_class_name);
zend_ast *class_name_ast = zend_ast_create_zval(&class_name_zval);

zval case_id_zval;
zend_long case_id = zend_enum_next_case_id(enum_class);
ZVAL_LONG(&case_id_zval, case_id);
zend_ast *case_id_ast = zend_ast_create_zval(&case_id_zval);

zval case_name_zval;
ZVAL_STR_COPY(&case_name_zval, enum_case_name);
zend_ast *case_name_ast = zend_ast_create_zval(&case_name_zval);
Expand All @@ -9582,7 +9587,8 @@ static void zend_compile_enum_case(zend_ast *ast)
ZSTR_VAL(enum_class_name));
}

zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_ast);
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT,
class_name_ast, case_id_ast, case_name_ast, case_value_ast);

zval value_zv;
zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false);
Expand Down Expand Up @@ -12448,7 +12454,7 @@ static void zend_eval_const_expr(zend_ast **ast_ptr) /* {{{ */
zend_eval_const_expr(&ast->child[1]);
return;
case ZEND_AST_CONST_ENUM_INIT:
zend_eval_const_expr(&ast->child[2]);
zend_eval_const_expr(&ast->child[3]);
return;
case ZEND_AST_PROP:
case ZEND_AST_NULLSAFE_PROP:
Expand Down
67 changes: 52 additions & 15 deletions Zend/zend_enum.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,16 @@ static zend_arg_info zarginfo_class_UnitEnum_cases[sizeof(arginfo_class_UnitEnum
static zend_arg_info zarginfo_class_BackedEnum_from[sizeof(arginfo_class_BackedEnum_from)/sizeof(zend_internal_arg_info)];
static zend_arg_info zarginfo_class_BackedEnum_tryFrom[sizeof(arginfo_class_BackedEnum_tryFrom)/sizeof(zend_internal_arg_info)];

zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv)
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_long case_id, zend_string *case_name, zval *backing_value_zv)
{
zend_object *zobj = zend_objects_new(ce);
zend_enum_obj *intern = zend_object_alloc(sizeof(zend_enum_obj), ce);

zend_object_std_init(&intern->std, ce);
object_properties_init(&intern->std, ce);

intern->case_id = case_id;

zend_object *zobj = &intern->std;
GC_ADD_FLAGS(zobj, GC_NOT_COLLECTABLE);
ZVAL_OBJ(result, zobj);

Expand Down Expand Up @@ -170,6 +177,7 @@ void zend_register_enum_ce(void)
zend_ce_backed_enum->interface_gets_implemented = zend_implement_backed_enum;

memcpy(&zend_enum_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
zend_enum_object_handlers.offset = XtOffsetOf(zend_enum_obj, std);
zend_enum_object_handlers.clone_obj = NULL;
zend_enum_object_handlers.compare = zend_objects_not_comparable;
}
Expand Down Expand Up @@ -533,20 +541,24 @@ ZEND_API zend_class_entry *zend_register_internal_enum(
zend_class_implements(ce, 1, zend_ce_backed_enum);
}

ce->default_object_handlers = &zend_enum_object_handlers;

return ce;
}

static zend_ast_ref *create_enum_case_ast(
zend_string *class_name, zend_string *case_name, zval *value) {
zend_string *class_name, zend_long case_id, zend_string *case_name,
zval *value) {
// TODO: Use custom node type for enum cases?
size_t size = sizeof(zend_ast_ref) + zend_ast_size(3)
+ (value ? 3 : 2) * sizeof(zend_ast_zval);
const size_t num_children = ZEND_AST_CONST_ENUM_INIT >> ZEND_AST_NUM_CHILDREN_SHIFT;
size_t size = sizeof(zend_ast_ref) + zend_ast_size(num_children)
+ (value ? num_children : num_children-1) * 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);
zend_ast *ast = (zend_ast *) p; p += zend_ast_size(num_children);
ast->kind = ZEND_AST_CONST_ENUM_INIT;
ast->attr = 0;
ast->lineno = 0;
Expand All @@ -561,24 +573,47 @@ static zend_ast_ref *create_enum_case_ast(
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);
ZVAL_LONG(zend_ast_get_zval(ast->child[1]), case_id);
Z_LINENO_P(zend_ast_get_zval(ast->child[1])) = 0;

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(ZSTR_IS_INTERNED(case_name));
ZVAL_STR(zend_ast_get_zval(ast->child[2]), case_name);
Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;

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;
ast->child[3] = (zend_ast *) p; p += sizeof(zend_ast_zval);
ast->child[3]->kind = ZEND_AST_ZVAL;
ast->child[3]->attr = 0;
ZEND_ASSERT(!Z_REFCOUNTED_P(value));
ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[2]), value);
Z_LINENO_P(zend_ast_get_zval(ast->child[2])) = 0;
ZVAL_COPY_VALUE(zend_ast_get_zval(ast->child[3]), value);
Z_LINENO_P(zend_ast_get_zval(ast->child[3])) = 0;
} else {
ast->child[2] = NULL;
ast->child[3] = NULL;
}

return ref;
}

zend_long zend_enum_next_case_id(zend_class_entry *enum_class)
{
ZEND_HASH_REVERSE_FOREACH_VAL(&enum_class->constants_table, zval *zv) {
zend_class_constant *c = Z_PTR_P(zv);
if (!(ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE)) {
continue;
}
ZEND_ASSERT(Z_TYPE(c->value) == IS_CONSTANT_AST);
zend_ast *ast = Z_ASTVAL(c->value);

ZEND_ASSERT(ast->kind == ZEND_AST_CONST_ENUM_INIT);
return Z_LVAL_P(zend_ast_get_zval(ast->child[1])) + 1;
} ZEND_HASH_FOREACH_END();

return 1;
}

ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, zval *value)
{
if (value) {
Expand All @@ -600,9 +635,11 @@ ZEND_API void zend_enum_add_case(zend_class_entry *ce, zend_string *case_name, z
ZEND_ASSERT(ce->enum_backing_type == IS_UNDEF);
}

zend_long case_id = zend_enum_next_case_id(ce);

zval ast_zv;
Z_TYPE_INFO(ast_zv) = IS_CONSTANT_AST;
Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_name, value);
Z_AST(ast_zv) = create_enum_case_ast(ce->name, case_id, 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;
Expand Down
19 changes: 18 additions & 1 deletion Zend/zend_enum.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,25 @@ extern ZEND_API zend_class_entry *zend_ce_unit_enum;
extern ZEND_API zend_class_entry *zend_ce_backed_enum;
extern ZEND_API zend_object_handlers zend_enum_object_handlers;

typedef struct _zend_enum_obj {
zend_long case_id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
zend_long case_id;
int case_id;

I believe enums are int (unless a type is specified, which is only possible as of C23). Also applies to zend_enum_next_case_id() and similar.

zend_object std;
} zend_enum_obj;

static inline zend_enum_obj *zend_enum_obj_from_obj(zend_object *zobj) {
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
return (zend_enum_obj*)((char*)(zobj) - XtOffsetOf(zend_enum_obj, std));
}

void zend_enum_startup(void);
void zend_register_enum_ce(void);
void zend_enum_add_interfaces(zend_class_entry *ce);
zend_result zend_enum_build_backed_enum_table(zend_class_entry *ce);
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv);
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_long case_id, zend_string *case_name, zval *backing_value_zv);
void zend_verify_enum(const zend_class_entry *ce);
void zend_enum_register_funcs(zend_class_entry *ce);
void zend_enum_register_props(zend_class_entry *ce);
zend_long zend_enum_next_case_id(zend_class_entry *enum_class);

ZEND_API zend_class_entry *zend_register_internal_enum(
const char *name, uint8_t type, const zend_function_entry *functions);
Expand All @@ -47,6 +58,12 @@ 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);
ZEND_API zend_result zend_enum_get_case_by_value(zend_object **result, zend_class_entry *ce, zend_long long_key, zend_string *string_key, bool try_from);

static zend_always_inline zend_long zend_enum_fetch_case_id(zend_object *zobj)
{
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assert is redundant with the one in zend_enum_obj_from_obj(). I also don't see how it could help with codegen.

return zend_enum_obj_from_obj(zobj)->case_id;
}

static zend_always_inline zval *zend_enum_fetch_case_name(zend_object *zobj)
{
ZEND_ASSERT(zobj->ce->ce_flags & ZEND_ACC_ENUM);
Expand Down
Loading
Loading