Skip to content

Commit ba3d8c4

Browse files
committed
Allow arbitrary const expressions in backed enums
Closes GH-7821 Closes GH-8418
1 parent d62d50b commit ba3d8c4

13 files changed

+122
-77
lines changed

Zend/tests/enum/backed-int-const-invalid-expr.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ var_dump(Foo::Bar->value);
1111

1212
?>
1313
--EXPECTF--
14-
Fatal error: Enum case value must be compile-time evaluatable in %s on line %d
14+
Fatal error: Constant expression contains invalid operations in %s on line %d

Zend/tests/enum/gh7821.phpt

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
--TEST--
2+
GH-7821: Can't use arbitrary constant expressions in enum cases
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
public const A = 'A';
8+
public const B = 'B';
9+
}
10+
11+
enum B: string implements I {
12+
case C = I::A;
13+
case D = self::B;
14+
}
15+
16+
var_dump(B::A);
17+
var_dump(B::B);
18+
var_dump(B::C->value);
19+
var_dump(B::D->value);
20+
21+
?>
22+
--EXPECT--
23+
string(1) "A"
24+
string(1) "B"
25+
string(1) "A"
26+
string(1) "B"

Zend/tests/enum/name-property.phpt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var_dump(IntFoo::Bar->name);
2323
--EXPECT--
2424
array(1) {
2525
[0]=>
26-
object(ReflectionProperty)#2 (2) {
26+
object(ReflectionProperty)#4 (2) {
2727
["name"]=>
2828
string(4) "name"
2929
["class"]=>
@@ -33,14 +33,14 @@ array(1) {
3333
string(3) "Bar"
3434
array(2) {
3535
[0]=>
36-
object(ReflectionProperty)#3 (2) {
36+
object(ReflectionProperty)#5 (2) {
3737
["name"]=>
3838
string(4) "name"
3939
["class"]=>
4040
string(6) "IntFoo"
4141
}
4242
[1]=>
43-
object(ReflectionProperty)#4 (2) {
43+
object(ReflectionProperty)#6 (2) {
4444
["name"]=>
4545
string(5) "value"
4646
["class"]=>

Zend/tests/enum/non-backed-enum-with-expr-value.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ enum Foo {
99

1010
?>
1111
--EXPECTF--
12-
Fatal error: Case Bar of non-backed enum Foo must not have a value, try adding ": int" to the enum declaration in %s on line %d
12+
Fatal error: Case Bar of non-backed enum Foo must not have a value in %s on line %d

Zend/tests/enum/non-backed-enum-with-int-value.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ enum Foo {
99

1010
?>
1111
--EXPECTF--
12-
Fatal error: Case Bar of non-backed enum Foo must not have a value, try adding ": int" to the enum declaration in %s on line %d
12+
Fatal error: Case Bar of non-backed enum Foo must not have a value in %s on line %d

Zend/tests/enum/non-backed-enum-with-string-value.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ enum Foo {
99

1010
?>
1111
--EXPECTF--
12-
Fatal error: Case Bar of non-backed enum Foo must not have a value, try adding ": string" to the enum declaration in %s on line %d
12+
Fatal error: Case Bar of non-backed enum Foo must not have a value in %s on line %d

Zend/zend_ast.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -776,12 +776,19 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
776776
zend_string *case_name = zend_ast_get_str(case_name_ast);
777777

778778
zend_ast *case_value_ast = ast->child[2];
779-
zval *case_value_zv = case_value_ast != NULL
780-
? zend_ast_get_zval(case_value_ast)
781-
: NULL;
779+
780+
zval case_value_zv;
781+
zval *case_value_zv_ptr = NULL;
782+
if (case_value_ast != NULL) {
783+
if (UNEXPECTED(zend_ast_evaluate(&case_value_zv, case_value_ast, scope) != SUCCESS)) {
784+
ret = FAILURE;
785+
break;
786+
}
787+
case_value_zv_ptr = &case_value_zv;
788+
}
782789

783790
zend_class_entry *ce = zend_lookup_class(class_name);
784-
zend_enum_new(result, ce, case_name, case_value_zv);
791+
zend_enum_new(result, ce, case_name, case_value_zv_ptr);
785792
break;
786793
}
787794
case ZEND_AST_CLASS_CONST:

Zend/zend_compile.c

Lines changed: 7 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7588,9 +7588,6 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_
75887588
ce->enum_backing_type = IS_STRING;
75897589
}
75907590
zend_type_release(type, 0);
7591-
7592-
ce->backed_enum_table = emalloc(sizeof(HashTable));
7593-
zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 0);
75947591
}
75957592

75967593
static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
@@ -7802,69 +7799,20 @@ static void zend_compile_enum_case(zend_ast *ast)
78027799
ZVAL_STR_COPY(&case_name_zval, enum_case_name);
78037800
zend_ast *case_name_ast = zend_ast_create_zval(&case_name_zval);
78047801

7805-
zend_ast *case_value_zval_ast = NULL;
78067802
zend_ast *case_value_ast = ast->child[1];
7803+
// Remove case_value_ast from the original AST to avoid freeing it, as it will be freed by zend_const_expr_to_zval
7804+
ast->child[1] = NULL;
78077805
if (enum_class->enum_backing_type != IS_UNDEF && case_value_ast == NULL) {
78087806
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of backed enum %s must have a value",
78097807
ZSTR_VAL(enum_case_name),
78107808
ZSTR_VAL(enum_class_name));
7811-
}
7812-
if (case_value_ast != NULL) {
7813-
zend_eval_const_expr(&ast->child[1]);
7814-
case_value_ast = ast->child[1];
7815-
if (case_value_ast->kind != ZEND_AST_ZVAL) {
7816-
zend_error_noreturn(
7817-
E_COMPILE_ERROR, "Enum case value must be compile-time evaluatable");
7818-
}
7819-
7820-
zval case_value_zv;
7821-
ZVAL_COPY(&case_value_zv, zend_ast_get_zval(case_value_ast));
7822-
if (enum_class->enum_backing_type == IS_UNDEF) {
7823-
if (Z_TYPE(case_value_zv) == IS_LONG || Z_TYPE(case_value_zv) == IS_STRING) {
7824-
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value, try adding \": %s\" to the enum declaration",
7825-
ZSTR_VAL(enum_case_name),
7826-
ZSTR_VAL(enum_class_name),
7827-
zend_zval_type_name(&case_value_zv));
7828-
} else {
7829-
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value",
7830-
ZSTR_VAL(enum_case_name),
7831-
ZSTR_VAL(enum_class_name));
7832-
}
7833-
}
7834-
7835-
if (enum_class->enum_backing_type != Z_TYPE(case_value_zv)) {
7836-
zend_error_noreturn(E_COMPILE_ERROR, "Enum case type %s does not match enum backing type %s",
7837-
zend_get_type_by_const(Z_TYPE(case_value_zv)),
7838-
zend_get_type_by_const(enum_class->enum_backing_type));
7839-
}
7840-
7841-
case_value_zval_ast = zend_ast_create_zval(&case_value_zv);
7842-
Z_TRY_ADDREF(case_name_zval);
7843-
if (enum_class->enum_backing_type == IS_LONG) {
7844-
zend_long long_key = Z_LVAL(case_value_zv);
7845-
zval *existing_case_name = zend_hash_index_find(enum_class->backed_enum_table, long_key);
7846-
if (existing_case_name) {
7847-
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
7848-
ZSTR_VAL(enum_class_name),
7849-
Z_STRVAL_P(existing_case_name),
7850-
ZSTR_VAL(enum_case_name));
7851-
}
7852-
zend_hash_index_add_new(enum_class->backed_enum_table, long_key, &case_name_zval);
7853-
} else {
7854-
ZEND_ASSERT(enum_class->enum_backing_type == IS_STRING);
7855-
zend_string *string_key = Z_STR(case_value_zv);
7856-
zval *existing_case_name = zend_hash_find(enum_class->backed_enum_table, string_key);
7857-
if (existing_case_name != NULL) {
7858-
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
7859-
ZSTR_VAL(enum_class_name),
7860-
Z_STRVAL_P(existing_case_name),
7861-
ZSTR_VAL(enum_case_name));
7862-
}
7863-
zend_hash_add_new(enum_class->backed_enum_table, string_key, &case_name_zval);
7864-
}
7809+
} else if (enum_class->enum_backing_type == IS_UNDEF && case_value_ast != NULL) {
7810+
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value",
7811+
ZSTR_VAL(enum_case_name),
7812+
ZSTR_VAL(enum_class_name));
78657813
}
78667814

7867-
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_zval_ast);
7815+
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_ast);
78687816

78697817
zval value_zv;
78707818
zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false);

Zend/zend_enum.c

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "zend_compile.h"
2222
#include "zend_enum_arginfo.h"
2323
#include "zend_interfaces.h"
24+
#include "zend_enum.h"
2425

2526
#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
2627
do { \
@@ -41,7 +42,7 @@ zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case
4142

4243
ZVAL_STR_COPY(OBJ_PROP_NUM(zobj, 0), case_name);
4344
if (backing_value_zv != NULL) {
44-
ZVAL_COPY(OBJ_PROP_NUM(zobj, 1), backing_value_zv);
45+
ZVAL_COPY_VALUE(OBJ_PROP_NUM(zobj, 1), backing_value_zv);
4546
}
4647

4748
zobj->handlers = &enum_handlers;
@@ -183,6 +184,64 @@ void zend_enum_add_interfaces(zend_class_entry *ce)
183184
}
184185
}
185186

187+
void zend_enum_build_backed_enum_table(zend_class_entry *ce)
188+
{
189+
uint32_t backing_type = ce->enum_backing_type;
190+
ZEND_ASSERT(backing_type != IS_UNDEF);
191+
192+
if (zend_update_class_constants(ce) == FAILURE) {
193+
// FIXME: What's the right way to error here?
194+
zend_error_noreturn(E_COMPILE_ERROR, "Could not update class constants");
195+
}
196+
197+
ce->backed_enum_table = emalloc(sizeof(HashTable));
198+
zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 0);
199+
200+
zend_string *enum_class_name = ce->name;
201+
202+
zend_string *name;
203+
zval *val;
204+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&ce->constants_table, name, val) {
205+
zend_class_constant *c = Z_PTR_P(val);
206+
if ((ZEND_CLASS_CONST_FLAGS(c) & ZEND_CLASS_CONST_IS_CASE) == 0) {
207+
continue;
208+
}
209+
210+
zval *c_value = &c->value;
211+
zval *case_name = zend_enum_fetch_case_name(Z_OBJ_P(c_value));
212+
zval *case_value = zend_enum_fetch_case_value(Z_OBJ_P(c_value));
213+
214+
if (ce->enum_backing_type != Z_TYPE_P(case_value)) {
215+
zend_error_noreturn(E_COMPILE_ERROR, "Enum case type %s does not match enum backing type %s",
216+
zend_get_type_by_const(Z_TYPE_P(case_value)),
217+
zend_get_type_by_const(ce->enum_backing_type));
218+
}
219+
220+
if (ce->enum_backing_type == IS_LONG) {
221+
zend_long long_key = Z_LVAL_P(case_value);
222+
zval *existing_case_name = zend_hash_index_find(ce->backed_enum_table, long_key);
223+
if (existing_case_name) {
224+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
225+
ZSTR_VAL(enum_class_name),
226+
Z_STRVAL_P(existing_case_name),
227+
ZSTR_VAL(name));
228+
}
229+
zend_hash_index_add_new(ce->backed_enum_table, long_key, case_name);
230+
} else {
231+
ZEND_ASSERT(ce->enum_backing_type == IS_STRING);
232+
zend_string *string_key = Z_STR_P(case_value);
233+
zval *existing_case_name = zend_hash_find(ce->backed_enum_table, string_key);
234+
if (existing_case_name != NULL) {
235+
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
236+
ZSTR_VAL(enum_class_name),
237+
Z_STRVAL_P(existing_case_name),
238+
ZSTR_VAL(name));
239+
}
240+
zend_hash_add_new(ce->backed_enum_table, string_key, case_name);
241+
}
242+
} ZEND_HASH_FOREACH_END();
243+
}
244+
186245
static ZEND_NAMED_FUNCTION(zend_enum_cases_func)
187246
{
188247
zend_class_entry *ce = execute_data->func->common.scope;

Zend/zend_enum.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern ZEND_API zend_class_entry *zend_ce_backed_enum;
2929

3030
void zend_register_enum_ce(void);
3131
void zend_enum_add_interfaces(zend_class_entry *ce);
32+
void zend_enum_build_backed_enum_table(zend_class_entry *ce);
3233
zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case_name, zval *backing_value_zv);
3334
void zend_verify_enum(zend_class_entry *ce);
3435
void zend_enum_register_funcs(zend_class_entry *ce);

Zend/zend_inheritance.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2933,6 +2933,10 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string
29332933
ZSTR_SET_CE_CACHE(ce->name, ce);
29342934
}
29352935

2936+
if (ce->ce_flags & ZEND_ACC_ENUM && ce->enum_backing_type != IS_UNDEF) {
2937+
zend_enum_build_backed_enum_table(ce);
2938+
}
2939+
29362940
return ce;
29372941
}
29382942
/* }}} */

ext/reflection/tests/ReflectionEnum_getCase.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,15 @@ test(IntEnum::class, 'Baz');
3232

3333
?>
3434
--EXPECT--
35-
object(ReflectionEnumUnitCase)#2 (2) {
35+
object(ReflectionEnumUnitCase)#3 (2) {
3636
["name"]=>
3737
string(3) "Foo"
3838
["class"]=>
3939
string(5) "Enum_"
4040
}
4141
ReflectionException: Enum_::Bar is not a case
4242
ReflectionException: Case Enum_::Baz does not exist
43-
object(ReflectionEnumBackedCase)#2 (2) {
43+
object(ReflectionEnumBackedCase)#3 (2) {
4444
["name"]=>
4545
string(3) "Foo"
4646
["class"]=>

ext/reflection/tests/ReflectionEnum_getCases.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ var_dump((new ReflectionEnum(IntEnum::class))->getCases());
2222
--EXPECT--
2323
array(2) {
2424
[0]=>
25-
object(ReflectionEnumUnitCase)#2 (2) {
25+
object(ReflectionEnumUnitCase)#4 (2) {
2626
["name"]=>
2727
string(3) "Foo"
2828
["class"]=>
2929
string(5) "Enum_"
3030
}
3131
[1]=>
32-
object(ReflectionEnumUnitCase)#3 (2) {
32+
object(ReflectionEnumUnitCase)#5 (2) {
3333
["name"]=>
3434
string(3) "Bar"
3535
["class"]=>
@@ -38,14 +38,14 @@ array(2) {
3838
}
3939
array(2) {
4040
[0]=>
41-
object(ReflectionEnumBackedCase)#2 (2) {
41+
object(ReflectionEnumBackedCase)#4 (2) {
4242
["name"]=>
4343
string(3) "Foo"
4444
["class"]=>
4545
string(7) "IntEnum"
4646
}
4747
[1]=>
48-
object(ReflectionEnumBackedCase)#1 (2) {
48+
object(ReflectionEnumBackedCase)#3 (2) {
4949
["name"]=>
5050
string(3) "Bar"
5151
["class"]=>

0 commit comments

Comments
 (0)