Skip to content

Commit 186e573

Browse files
committed
Allow arbitrary const expressions in backed enums
Closes GH-7821
1 parent ddb04d4 commit 186e573

15 files changed

+146
-96
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/tests/enum/test.phpt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
GH-7821: Can't use arbitrary constant expressiosn in enum cases
3+
--FILE--
4+
<?php
5+
6+
interface I {
7+
public const A = 'A';
8+
}
9+
10+
class B implements I {
11+
const B = self::A;
12+
}
13+
14+
new B();
15+
16+
?>
17+
--EXPECT--

Zend/zend_ast.c

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -770,9 +770,16 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
770770
zend_string *case_name = zend_ast_get_str(case_name_ast);
771771

772772
zend_ast *case_value_ast = ast->child[2];
773-
zval *case_value_zv = case_value_ast != NULL
774-
? zend_ast_get_zval(case_value_ast)
775-
: NULL;
773+
774+
zval case_value_zv;
775+
zval *case_value_zv_ptr = NULL;
776+
if (case_value_ast != NULL) {
777+
if (UNEXPECTED(zend_ast_evaluate(&case_value_zv, case_value_ast, scope) != SUCCESS)) {
778+
ret = FAILURE;
779+
break;
780+
}
781+
case_value_zv_ptr = &case_value_zv;
782+
}
776783

777784
zend_class_entry *ce = zend_lookup_class(class_name);
778785
if (!ce) {
@@ -782,7 +789,7 @@ ZEND_API zend_result ZEND_FASTCALL zend_ast_evaluate(zval *result, zend_ast *ast
782789
return FAILURE;
783790
}
784791

785-
zend_enum_new(result, ce, case_name, case_value_zv);
792+
zend_enum_new(result, ce, case_name, case_value_zv_ptr);
786793
break;
787794
}
788795
case ZEND_AST_CLASS_CONST:

Zend/zend_compile.c

Lines changed: 5 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -7571,9 +7571,6 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_
75717571
ce->enum_backing_type = IS_STRING;
75727572
}
75737573
zend_type_release(type, 0);
7574-
7575-
ce->backed_enum_table = emalloc(sizeof(HashTable));
7576-
zend_hash_init(ce->backed_enum_table, 0, NULL, ZVAL_PTR_DTOR, 0);
75777574
}
75787575

75797576
static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
@@ -7785,69 +7782,18 @@ static void zend_compile_enum_case(zend_ast *ast)
77857782
ZVAL_STR_COPY(&case_name_zval, enum_case_name);
77867783
zend_ast *case_name_ast = zend_ast_create_zval(&case_name_zval);
77877784

7788-
zend_ast *case_value_zval_ast = NULL;
77897785
zend_ast *case_value_ast = ast->child[1];
77907786
if (enum_class->enum_backing_type != IS_UNDEF && case_value_ast == NULL) {
77917787
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of backed enum %s must have a value",
77927788
ZSTR_VAL(enum_case_name),
77937789
ZSTR_VAL(enum_class_name));
7794-
}
7795-
if (case_value_ast != NULL) {
7796-
zend_eval_const_expr(&ast->child[1]);
7797-
case_value_ast = ast->child[1];
7798-
if (case_value_ast->kind != ZEND_AST_ZVAL) {
7799-
zend_error_noreturn(
7800-
E_COMPILE_ERROR, "Enum case value must be compile-time evaluatable");
7801-
}
7802-
7803-
zval case_value_zv;
7804-
ZVAL_COPY(&case_value_zv, zend_ast_get_zval(case_value_ast));
7805-
if (enum_class->enum_backing_type == IS_UNDEF) {
7806-
if (Z_TYPE(case_value_zv) == IS_LONG || Z_TYPE(case_value_zv) == IS_STRING) {
7807-
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",
7808-
ZSTR_VAL(enum_case_name),
7809-
ZSTR_VAL(enum_class_name),
7810-
zend_zval_type_name(&case_value_zv));
7811-
} else {
7812-
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value",
7813-
ZSTR_VAL(enum_case_name),
7814-
ZSTR_VAL(enum_class_name));
7815-
}
7816-
}
7817-
7818-
if (enum_class->enum_backing_type != Z_TYPE(case_value_zv)) {
7819-
zend_error_noreturn(E_COMPILE_ERROR, "Enum case type %s does not match enum backing type %s",
7820-
zend_get_type_by_const(Z_TYPE(case_value_zv)),
7821-
zend_get_type_by_const(enum_class->enum_backing_type));
7822-
}
7823-
7824-
case_value_zval_ast = zend_ast_create_zval(&case_value_zv);
7825-
Z_TRY_ADDREF(case_name_zval);
7826-
if (enum_class->enum_backing_type == IS_LONG) {
7827-
zend_long long_key = Z_LVAL(case_value_zv);
7828-
zval *existing_case_name = zend_hash_index_find(enum_class->backed_enum_table, long_key);
7829-
if (existing_case_name) {
7830-
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
7831-
ZSTR_VAL(enum_class_name),
7832-
Z_STRVAL_P(existing_case_name),
7833-
ZSTR_VAL(enum_case_name));
7834-
}
7835-
zend_hash_index_add_new(enum_class->backed_enum_table, long_key, &case_name_zval);
7836-
} else {
7837-
ZEND_ASSERT(enum_class->enum_backing_type == IS_STRING);
7838-
zend_string *string_key = Z_STR(case_value_zv);
7839-
zval *existing_case_name = zend_hash_find(enum_class->backed_enum_table, string_key);
7840-
if (existing_case_name != NULL) {
7841-
zend_error_noreturn(E_COMPILE_ERROR, "Duplicate value in enum %s for cases %s and %s",
7842-
ZSTR_VAL(enum_class_name),
7843-
Z_STRVAL_P(existing_case_name),
7844-
ZSTR_VAL(enum_case_name));
7845-
}
7846-
zend_hash_add_new(enum_class->backed_enum_table, string_key, &case_name_zval);
7847-
}
7790+
} else if (enum_class->enum_backing_type == IS_UNDEF && case_value_ast != NULL) {
7791+
zend_error_noreturn(E_COMPILE_ERROR, "Case %s of non-backed enum %s must not have a value",
7792+
ZSTR_VAL(enum_case_name),
7793+
ZSTR_VAL(enum_class_name));
78487794
}
78497795

7850-
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_zval_ast);
7796+
zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_ast);
78517797

78527798
zval value_zv;
78537799
zend_const_expr_to_zval(&value_zv, &const_enum_init_ast, /* allow_dynamic */ false);

Zend/zend_constants.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,7 +330,8 @@ ZEND_API zval *zend_get_class_constant_ex(zend_string *class_name, zend_string *
330330
zend_class_constant *c = NULL;
331331
zval *ret_constant = NULL;
332332

333-
if (ZSTR_HAS_CE_CACHE(class_name)) {
333+
// FIXME: Why does "self" have the CE cache?
334+
if (false && ZSTR_HAS_CE_CACHE(class_name)) {
334335
ce = ZSTR_GET_CE_CACHE(class_name);
335336
if (!ce) {
336337
ce = zend_fetch_class(class_name, flags);

0 commit comments

Comments
 (0)