From a9ccf83b134ba05231094c5ca6b0a89fcb786199 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Wed, 4 Sep 2024 02:05:12 +0200 Subject: [PATCH 1/6] Implement ADTs --- Zend/tests/enum/adt/argument_count_error.phpt | 53 +++++ Zend/tests/enum/adt/argument_type_error.phpt | 65 ++++++ .../enum/adt/assoc_values_on_backed.phpt | 12 + Zend/tests/enum/adt/basic.phpt | 44 ++++ Zend/tests/enum/adt/ctor_cpp.phpt | 12 + Zend/tests/enum/adt/ctor_hooks.phpt | 12 + Zend/tests/enum/adt/ctor_ref.phpt | 12 + Zend/tests/enum/adt/ctor_variadics.phpt | 12 + Zend/tests/enum/adt/match_error_message.phpt | 17 ++ .../tests/enum/adt/parameter_count_error.phpt | 53 +++++ Zend/tests/enum/adt/var_export.phpt | 26 +++ Zend/tests/enum/final.phpt | 2 +- Zend/zend.h | 1 + Zend/zend_ast.h | 2 +- Zend/zend_compile.c | 208 ++++++++++++++++-- Zend/zend_enum.c | 34 +-- Zend/zend_enum.h | 2 + Zend/zend_execute.c | 4 +- Zend/zend_execute.h | 2 + Zend/zend_inheritance.c | 21 +- Zend/zend_language_parser.y | 6 +- Zend/zend_smart_str.c | 6 +- ext/standard/var.c | 106 +++++---- 23 files changed, 618 insertions(+), 94 deletions(-) create mode 100644 Zend/tests/enum/adt/argument_count_error.phpt create mode 100644 Zend/tests/enum/adt/argument_type_error.phpt create mode 100644 Zend/tests/enum/adt/assoc_values_on_backed.phpt create mode 100644 Zend/tests/enum/adt/basic.phpt create mode 100644 Zend/tests/enum/adt/ctor_cpp.phpt create mode 100644 Zend/tests/enum/adt/ctor_hooks.phpt create mode 100644 Zend/tests/enum/adt/ctor_ref.phpt create mode 100644 Zend/tests/enum/adt/ctor_variadics.phpt create mode 100644 Zend/tests/enum/adt/match_error_message.phpt create mode 100644 Zend/tests/enum/adt/parameter_count_error.phpt create mode 100644 Zend/tests/enum/adt/var_export.phpt diff --git a/Zend/tests/enum/adt/argument_count_error.phpt b/Zend/tests/enum/adt/argument_count_error.phpt new file mode 100644 index 0000000000000..5755b89c32ce8 --- /dev/null +++ b/Zend/tests/enum/adt/argument_count_error.phpt @@ -0,0 +1,53 @@ +--TEST-- +ADT argument count error +--FILE-- +getMessage(), "\n"; + } +} + +test(fn() => E::C()); +test(fn() => E::C(1)); +test(fn() => E::C(1, 2)); +test(fn() => E::C(1, 2, 3)); +test(fn() => E::C(x: 1)); +test(fn() => E::C(y: 2)); +test(fn() => E::C(x: 1, y: 2)); +test(fn() => E::C(y: 2, x: 1)); +test(fn() => E::C(1, 2, z: 3)); + +?> +--EXPECT-- +ArgumentCountError: E::C() expects exactly 2 arguments, 0 given +ArgumentCountError: E::C() expects exactly 2 arguments, 1 given +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +ArgumentCountError: E::C() expects exactly 2 arguments, 3 given +ArgumentCountError: E::C() expects exactly 2 arguments, 1 given +ArgumentCountError: E::C(): Argument #1 ($x) not passed +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +Error: Unknown named parameter $z diff --git a/Zend/tests/enum/adt/argument_type_error.phpt b/Zend/tests/enum/adt/argument_type_error.phpt new file mode 100644 index 0000000000000..c753e493f19a0 --- /dev/null +++ b/Zend/tests/enum/adt/argument_type_error.phpt @@ -0,0 +1,65 @@ +--TEST-- +ADT argument type error +--FILE-- +getMessage(), "\n"; + } +} + +test(fn() => E::C(1, 2)); + +test(fn() => E::C('1', 2)); +test(fn() => E::C('1.1', 2)); +test(fn() => E::C([], [])); + +test(fn() => E::C(1, '2')); +test(fn() => E::C(1, '2.2')); +test(fn() => E::C(1, [])); + +?> +--EXPECTF-- +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} + +Deprecated: Implicit conversion from float-string "1.1" to int loses precision in %s on line %d +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +TypeError: E::C(): Argument #1 ($x) must be of type int, array given, called in %s on line %d +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} + +Deprecated: Implicit conversion from float-string "2.2" to int loses precision in %s on line %d +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +TypeError: E::C(): Argument #2 ($y) must be of type int, array given, called in %s on line %d diff --git a/Zend/tests/enum/adt/assoc_values_on_backed.phpt b/Zend/tests/enum/adt/assoc_values_on_backed.phpt new file mode 100644 index 0000000000000..5fdbf305137d5 --- /dev/null +++ b/Zend/tests/enum/adt/assoc_values_on_backed.phpt @@ -0,0 +1,12 @@ +--TEST-- +Backed enum must not have assoc values +--FILE-- + +--EXPECTF-- +Fatal error: Case A of backed enum E must not have associated values in %s on line %d diff --git a/Zend/tests/enum/adt/basic.phpt b/Zend/tests/enum/adt/basic.phpt new file mode 100644 index 0000000000000..527f06daf1726 --- /dev/null +++ b/Zend/tests/enum/adt/basic.phpt @@ -0,0 +1,44 @@ +--TEST-- +Basic ADT +--FILE-- +x); + +$c = E::C('foo'); +var_dump($c, $c->x); + +$d = E::D('bar', 43); +var_dump($d, $d->x, $d->y); + +?> +--EXPECT-- +enum(E::A) +enum(E::B) (1) { + ["x"]=> + int(42) +} +int(42) +enum(E::C) (1) { + ["x"]=> + string(3) "foo" +} +string(3) "foo" +enum(E::D) (2) { + ["x"]=> + string(3) "bar" + ["y"]=> + int(43) +} +string(3) "bar" +int(43) diff --git a/Zend/tests/enum/adt/ctor_cpp.phpt b/Zend/tests/enum/adt/ctor_cpp.phpt new file mode 100644 index 0000000000000..a97b208ae65ac --- /dev/null +++ b/Zend/tests/enum/adt/ctor_cpp.phpt @@ -0,0 +1,12 @@ +--TEST-- +ADT assoc value must not be promoted +--FILE-- + +--EXPECTF-- +Fatal error: Associated value $x of enum case E::A must not be promoted in %s on line %d diff --git a/Zend/tests/enum/adt/ctor_hooks.phpt b/Zend/tests/enum/adt/ctor_hooks.phpt new file mode 100644 index 0000000000000..dcf36815afe18 --- /dev/null +++ b/Zend/tests/enum/adt/ctor_hooks.phpt @@ -0,0 +1,12 @@ +--TEST-- +ADT assoc value must not contain hooks +--FILE-- + 42; }); +} + +?> +--EXPECTF-- +Fatal error: Associated value $x of enum case E::A must not be promoted in %s on line %d diff --git a/Zend/tests/enum/adt/ctor_ref.phpt b/Zend/tests/enum/adt/ctor_ref.phpt new file mode 100644 index 0000000000000..35387d591b7d8 --- /dev/null +++ b/Zend/tests/enum/adt/ctor_ref.phpt @@ -0,0 +1,12 @@ +--TEST-- +ADT assoc value must not be pass-by-reference +--FILE-- + +--EXPECTF-- +Fatal error: Associated value $x of enum case E::A must not be promoted in %s on line %d diff --git a/Zend/tests/enum/adt/ctor_variadics.phpt b/Zend/tests/enum/adt/ctor_variadics.phpt new file mode 100644 index 0000000000000..7fa7b3e08056c --- /dev/null +++ b/Zend/tests/enum/adt/ctor_variadics.phpt @@ -0,0 +1,12 @@ +--TEST-- +ADT ctor must not contain variadics +--FILE-- + +--EXPECTF-- +Fatal error: Associated value $args of enum case E::A must not be variadic in %s on line %d diff --git a/Zend/tests/enum/adt/match_error_message.phpt b/Zend/tests/enum/adt/match_error_message.phpt new file mode 100644 index 0000000000000..9c302b0710f02 --- /dev/null +++ b/Zend/tests/enum/adt/match_error_message.phpt @@ -0,0 +1,17 @@ +--TEST-- +ADTs in match error message +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught UnhandledMatchError: Unhandled match case E::A in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/enum/adt/parameter_count_error.phpt b/Zend/tests/enum/adt/parameter_count_error.phpt new file mode 100644 index 0000000000000..80aba52ee639b --- /dev/null +++ b/Zend/tests/enum/adt/parameter_count_error.phpt @@ -0,0 +1,53 @@ +--TEST-- +ADT parameter count validation +--FILE-- +getMessage(), "\n"; + } +} + +test(fn() => E::C()); +test(fn() => E::C(1)); +test(fn() => E::C(1, 2)); +test(fn() => E::C(1, 2, 3)); +test(fn() => E::C(x: 1)); +test(fn() => E::C(y: 2)); +test(fn() => E::C(x: 1, y: 2)); +test(fn() => E::C(y: 2, x: 1)); +test(fn() => E::C(1, 2, z: 3)); + +?> +--EXPECT-- +ArgumentCountError: E::C() expects exactly 2 arguments, 0 given +ArgumentCountError: E::C() expects exactly 2 arguments, 1 given +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +ArgumentCountError: E::C() expects exactly 2 arguments, 3 given +ArgumentCountError: E::C() expects exactly 2 arguments, 1 given +ArgumentCountError: E::C(): Argument #1 ($x) not passed +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +enum(E::C) (2) { + ["x"]=> + int(1) + ["y"]=> + int(2) +} +Error: Unknown named parameter $z diff --git a/Zend/tests/enum/adt/var_export.phpt b/Zend/tests/enum/adt/var_export.phpt new file mode 100644 index 0000000000000..ebc9e7fd3d3fd --- /dev/null +++ b/Zend/tests/enum/adt/var_export.phpt @@ -0,0 +1,26 @@ +--TEST-- +Basic ADT +--FILE-- + +--EXPECT-- +\E::A +\E::B(42) +\E::C('foo') +\E::D(43, 'bar') diff --git a/Zend/tests/enum/final.phpt b/Zend/tests/enum/final.phpt index 353e1868d2fa8..9afd4e7a47d5a 100644 --- a/Zend/tests/enum/final.phpt +++ b/Zend/tests/enum/final.phpt @@ -9,4 +9,4 @@ class Bar extends Foo {} ?> --EXPECTF-- -Fatal error: Class Bar cannot extend final class Foo in %s on line %d +Fatal error: Class Bar cannot extend enum Foo in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 4fe0703d42f69..735fd419f9acf 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -219,6 +219,7 @@ struct _zend_class_entry { HashTable *attributes; uint32_t enum_backing_type; + uint32_t enum_flags; HashTable *backed_enum_table; zend_string *doc_comment; diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 8d1d93f2564a7..2d9cb70e5e1f2 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -171,10 +171,10 @@ enum _zend_ast_kind { /* 4 child nodes */ ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH, - ZEND_AST_ENUM_CASE, ZEND_AST_PROP_ELEM, /* 5 child nodes */ + ZEND_AST_ENUM_CASE = 5 << ZEND_AST_NUM_CHILDREN_SHIFT, /* 6 child nodes */ ZEND_AST_PARAM = 6 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 63787c902f647..f616bae7ff7de 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -101,6 +101,7 @@ static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t static void zend_compile_expr(znode *result, zend_ast *ast); static void zend_compile_stmt(zend_ast *ast); static void zend_compile_assign(znode *result, zend_ast *ast); +static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_decl *enum_decl, bool toplevel); #ifdef ZEND_CHECK_STACK_LIMIT zend_never_inline static void zend_stack_limit_error(void) @@ -2072,6 +2073,7 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->properties_info_table = NULL; ce->attributes = NULL; ce->enum_backing_type = IS_UNDEF; + ce->enum_flags = 0; ce->backed_enum_table = NULL; if (nullify_handlers) { @@ -5379,7 +5381,7 @@ static void zend_compile_static_call(znode *result, zend_ast *ast, uint32_t type } /* }}} */ -static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel); +static zend_class_entry *zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel); static void zend_compile_new(znode *result, zend_ast *ast) /* {{{ */ { @@ -8976,7 +8978,7 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_ zend_type_release(type, 0); } -static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ +static zend_class_entry *zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */ { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *extends_ast = decl->child[0]; @@ -9101,7 +9103,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) && !zend_compile_ignore_class(parent_ce, ce->info.user.filename)) { if (zend_try_early_bind(ce, parent_ce, lcname, NULL)) { zend_string_release(lcname); - return; + return ce; } } } else if (EXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL)) { @@ -9110,7 +9112,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) zend_inheritance_check_override(ce); ce->ce_flags |= ZEND_ACC_LINKED; zend_observer_class_linked_notify(ce, lcname); - return; + return ce; } else { goto link_unbound; } @@ -9174,6 +9176,17 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) opline->result.opline_num = -1; } } + + if (ce->ce_flags & ZEND_ACC_ENUM) { + if (ce->enum_flags & ZEND_ENUM_FLAGS_ADT) { + /* Declaration needs to be emitted after declaration of the parent enum. */ + zend_compile_enum_assoc_cases(ce, decl, toplevel); + } else { + ce->ce_flags |= ZEND_ACC_FINAL; + } + } + + return ce; } /* }}} */ @@ -9187,17 +9200,7 @@ static void zend_compile_enum_case(zend_ast *ast) zend_string *enum_case_name = zval_make_interned_string(zend_ast_get_zval(ast->child[0])); zend_string *enum_class_name = enum_class->name; - zval class_name_zval; - ZVAL_STR_COPY(&class_name_zval, enum_class_name); - zend_ast *class_name_ast = zend_ast_create_zval(&class_name_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); - zend_ast *case_value_ast = ast->child[1]; - // Remove case_value_ast from the original AST to avoid freeing it, as it will be freed by zend_const_expr_to_zval - ast->child[1] = NULL; if (enum_class->enum_backing_type != IS_UNDEF && case_value_ast == NULL) { zend_error_noreturn(E_COMPILE_ERROR, "Case %s of backed enum %s must have a value", ZSTR_VAL(enum_case_name), @@ -9208,6 +9211,29 @@ static void zend_compile_enum_case(zend_ast *ast) ZSTR_VAL(enum_class_name)); } + zend_ast *assoc_values_ast = ast->child[4]; + if (assoc_values_ast) { + if (enum_class->enum_backing_type != IS_UNDEF) { + zend_error_noreturn(E_COMPILE_ERROR, "Case %s of backed enum %s must not have associated values", + ZSTR_VAL(enum_case_name), + ZSTR_VAL(enum_class_name)); + } + enum_class->enum_flags |= ZEND_ENUM_FLAGS_ADT; + /* ADT cases are compiled after the base enum in a second pass, to preserve declaration order. */ + return; + } + + zval class_name_zval; + ZVAL_STR_COPY(&class_name_zval, enum_class_name); + zend_ast *class_name_ast = zend_ast_create_zval(&class_name_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); + + // Remove case_value_ast from the original AST to avoid freeing it, as it will be freed by zend_const_expr_to_zval + ast->child[1] = NULL; + zend_ast *const_enum_init_ast = zend_ast_create(ZEND_AST_CONST_ENUM_INIT, class_name_ast, case_name_ast, case_value_ast); zval value_zv; @@ -9236,6 +9262,160 @@ static void zend_compile_enum_case(zend_ast *ast) } } +static ZEND_NAMED_FUNCTION(zend_enum_adt_constructor) +{ + zend_internal_function *func = &EX(func)->internal_function; + uint32_t num_args = EX_NUM_ARGS(); + if (UNEXPECTED(num_args < func->required_num_args || func->num_args < num_args)) { + zend_wrong_parameters_count_error(func->required_num_args, func->num_args); + RETURN_THROWS(); + } + + zend_class_entry *case_ce = EX(func)->internal_function.reserved[0]; + zend_object *zobj = zend_objects_new(case_ce); + zval *prop = OBJ_PROP_NUM(zobj, 0); + + ZVAL_STR_COPY(prop, EX(func)->common.function_name); + Z_PROP_FLAG_P(prop) = 0; + prop++; + + zval *arg = ZEND_CALL_ARG(execute_data, 1); + for (uint32_t i = 1; i <= EX_NUM_ARGS(); i++) { + if (UNEXPECTED(!zend_verify_recv_arg_type(EX(func), i, arg, NULL))) { + for (; i <= EX_NUM_ARGS(); i++) { + ZVAL_UNDEF(prop); + Z_PROP_FLAG_P(prop) = 0; + prop++; + } + GC_DELREF(zobj); + ZEND_ASSERT(GC_REFCOUNT(zobj) == 0); + zend_objects_store_del(zobj); + RETVAL_NULL(); + RETURN_THROWS(); + } + + ZVAL_COPY(prop, arg); + Z_PROP_FLAG_P(prop) = 0; + arg++; + prop++; + } + + RETURN_OBJ(zobj); +} + +static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_decl *enum_decl, bool toplevel) +{ + zend_ast *stmt_ast = enum_decl->child[2]; + zend_ast_list *list = zend_ast_get_list(stmt_ast); + uint32_t i; + for (i = 0; i < list->children; ++i) { + zend_ast *ast = list->child[i]; + if (ast->kind != ZEND_AST_ENUM_CASE || !ast->child[4]) { + continue; + } + + zend_string *case_name = zval_make_interned_string(zend_ast_get_zval(ast->child[0])); + + zend_string *case_ce_name = zend_new_interned_string(zend_string_concat3( + ZSTR_VAL(enum_ce->name), ZSTR_LEN(enum_ce->name), + ZEND_STRL("::"), + ZSTR_VAL(case_name), ZSTR_LEN(case_name))); + + zend_ast *parent_name_ast = zend_ast_create_zval_from_str(enum_ce->name); + parent_name_ast->attr = ZEND_NAME_FQ; + + zend_ast *stmt_list = zend_ast_create_list(0, ZEND_AST_STMT_LIST); + + zend_ast *case_ast_decl = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM, ast->lineno, /* doc_comment: FIXME */ NULL, + case_ce_name, parent_name_ast, NULL, stmt_list, NULL, NULL); + + zend_class_entry *case_ce = zend_compile_class_decl(NULL, case_ast_decl, toplevel); + + zval default_value; + ZVAL_UNDEF(&default_value); + + zend_ast_list *param_list = zend_ast_get_list(ast->child[4]); + zend_internal_arg_info *arg_infos = zend_arena_calloc(&CG(arena), sizeof(zend_internal_arg_info), param_list->children + 1); + + zend_internal_arg_info *arg_info = arg_infos; + + arg_info->name = NULL; // FIXME: Needs required arg count? + // arg_info->type = (zend_type) ZEND_TYPE_INIT_CLASS(case_ce->name, false, 0); + arg_info->type = (zend_type) ZEND_TYPE_INIT_CODE(IS_MIXED, 0, 0); + arg_info->default_value = NULL; + arg_info++; + + for (uint32_t i = 0; i < param_list->children; i++) { + zend_ast *param_ast = param_list->child[i]; + zend_ast *param_type_ast = param_ast->child[0]; + zend_string *param_name = zval_make_interned_string(zend_ast_get_zval(param_ast->child[1])); + zend_type param_type = ZEND_TYPE_INIT_NONE(0); + zend_ast *hooks_ast = param_ast->child[5]; + + if (param_ast->attr & ZEND_PARAM_VARIADIC) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated value $%s of enum case %s must not be variadic", + ZSTR_VAL(param_name), ZSTR_VAL(case_ce_name)); + } + if ((param_ast->attr & (ZEND_ACC_PPP_MASK|ZEND_ACC_PPP_SET_MASK|ZEND_ACC_READONLY)) || hooks_ast) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated value $%s of enum case %s must not be promoted", + ZSTR_VAL(param_name), ZSTR_VAL(case_ce_name)); + } + if (param_ast->attr & ZEND_PARAM_REF) { + zend_error_noreturn(E_COMPILE_ERROR, "Associated value $%s of enum case %s must not be pass-by-reference", + ZSTR_VAL(param_name), ZSTR_VAL(case_ce_name)); + } + + if (param_type_ast) { + param_type = zend_compile_typename(param_type_ast); + } + + zend_declare_typed_property( + case_ce, param_name, &default_value, + ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, + /* doc_comment: FIXME */ NULL, param_type); + + // FIXME: This pointer is volatile with opcache... + arg_info->name = (const char *) ZSTR_VAL(param_name); + arg_info->type = param_type; + arg_info->default_value = NULL; + + arg_info++; + } + + const uint32_t fn_flags = ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED; + zend_internal_function *func = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); + func->handler = zend_enum_adt_constructor; + func->function_name = zend_string_copy(case_name); + func->fn_flags = fn_flags; + func->doc_comment = NULL; + + func->num_args = param_list->children; + func->required_num_args = param_list->children; + func->arg_info = arg_infos + 1; + + func->type = ZEND_INTERNAL_FUNCTION; + func->module = EG(current_module); + func->scope = enum_ce; + func->T = ZEND_OBSERVER_ENABLED; + + /* FIXME: Volatile pointer. */ + func->reserved[0] = case_ce; + + ZEND_MAP_PTR_NEW(func->run_time_cache); + //if (EG(active)) { // at run-time + // ZEND_UNREACHABLE(); + // // ZEND_MAP_PTR_INIT(func->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size())); + //} else { + // ZEND_MAP_PTR_NEW(func->run_time_cache); + //} + + zend_string *case_name_lc = zend_new_interned_string(zend_string_tolower(case_name)); + if (!zend_hash_add_ptr(&enum_ce->function_table, case_name_lc, func)) { + zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s()", ZSTR_VAL(case_ce_name)); + } + } +} + static HashTable *zend_get_import_ht(uint32_t type) /* {{{ */ { switch (type) { diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 309d7da1cf7aa..b494bf40e536b 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -57,26 +57,6 @@ zend_object *zend_enum_new(zval *result, zend_class_entry *ce, zend_string *case return zobj; } -static void zend_verify_enum_properties(zend_class_entry *ce) -{ - zend_property_info *property_info; - - ZEND_HASH_MAP_FOREACH_PTR(&ce->properties_info, property_info) { - if (zend_string_equals(property_info->name, ZSTR_KNOWN(ZEND_STR_NAME))) { - continue; - } - if ( - ce->enum_backing_type != IS_UNDEF - && zend_string_equals(property_info->name, ZSTR_KNOWN(ZEND_STR_VALUE)) - ) { - continue; - } - // FIXME: File/line number for traits? - zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include properties", - ZSTR_VAL(ce->name)); - } ZEND_HASH_FOREACH_END(); -} - static void zend_verify_enum_magic_methods(zend_class_entry *ce) { // Only __get, __call and __invoke are allowed @@ -119,7 +99,6 @@ static void zend_verify_enum_interfaces(zend_class_entry *ce) void zend_verify_enum(zend_class_entry *ce) { - zend_verify_enum_properties(ce); zend_verify_enum_magic_methods(ce); zend_verify_enum_interfaces(ce); } @@ -171,6 +150,12 @@ void zend_register_enum_ce(void) void zend_enum_add_interfaces(zend_class_entry *ce) { + ce->default_object_handlers = &zend_enum_object_handlers; + + if ((ce->enum_flags & ZEND_ENUM_FLAGS_ADT) || ce->parent_name) { + return; + } + uint32_t num_interfaces_before = ce->num_interfaces; ce->num_interfaces++; @@ -189,8 +174,6 @@ void zend_enum_add_interfaces(zend_class_entry *ce) ce->interface_names[num_interfaces_before + 1].name = zend_string_copy(zend_ce_backed_enum->name); ce->interface_names[num_interfaces_before + 1].lc_name = ZSTR_INIT_LITERAL("backedenum", 0); } - - ce->default_object_handlers = &zend_enum_object_handlers; } zend_result zend_enum_build_backed_enum_table(zend_class_entry *ce) @@ -468,6 +451,11 @@ void zend_enum_register_props(zend_class_entry *ce) { ce->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; + if (ce->parent_name) { + /* This is an ADT case, the name property is inherited from the parent. */ + return; + } + zval name_default_value; ZVAL_UNDEF(&name_default_value); zend_type name_type = ZEND_TYPE_INIT_CODE(IS_STRING, 0, 0); diff --git a/Zend/zend_enum.h b/Zend/zend_enum.h index c3519ee35f3bb..c82b178b3a286 100644 --- a/Zend/zend_enum.h +++ b/Zend/zend_enum.h @@ -26,6 +26,8 @@ BEGIN_EXTERN_C() +#define ZEND_ENUM_FLAGS_ADT (1 << 0) + 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; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 733ce54dc24ad..066254234c5e8 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -706,8 +706,6 @@ ZEND_API ZEND_COLD void zend_verify_arg_error( zend_verify_type_error_common( zf, arg_info, value, &fname, &fsep, &fclass, &need_msg, &given_msg); - ZEND_ASSERT(zf->common.type == ZEND_USER_FUNCTION - && "Arginfo verification is not performed for internal functions"); if (ptr && ptr->func && ZEND_USER_CODE(ptr->func->common.type)) { zend_argument_type_error(arg_num, "must be of type %s, %s given, called in %s on line %d", ZSTR_VAL(need_msg), given_msg, @@ -1252,7 +1250,7 @@ ZEND_API bool zend_check_user_type_slow( type, arg, ref, cache_slot, is_return_type, /* is_internal */ false); } -static zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) +ZEND_API zend_always_inline bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot) { zend_arg_info *cur_arg_info; diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 190d67f82a598..1a228cf74de83 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -513,6 +513,8 @@ ZEND_API bool zend_verify_property_type(const zend_property_info *info, zval *pr ZEND_COLD void zend_verify_property_type_error(const zend_property_info *info, const zval *property); ZEND_COLD void zend_magic_get_property_type_inconsistency_error(const zend_property_info *info, const zval *property); +ZEND_API bool zend_verify_recv_arg_type(zend_function *zf, uint32_t arg_num, zval *arg, void **cache_slot); + #define ZEND_REF_ADD_TYPE_SOURCE(ref, source) \ zend_ref_add_type_source(&ZEND_REF_TYPE_SOURCES(ref), source) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 732eb141eb271..0c6393d74bcd0 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1757,18 +1757,18 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par if (UNEXPECTED(!(parent_ce->ce_flags & ZEND_ACC_INTERFACE))) { zend_error_noreturn(E_COMPILE_ERROR, "Interface %s cannot extend class %s", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name)); } - } else if (UNEXPECTED(parent_ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_FINAL))) { - /* Class must not extend a final class */ - if (parent_ce->ce_flags & ZEND_ACC_FINAL) { - zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot extend final class %s", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name)); - } - + } else if (UNEXPECTED(parent_ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_FINAL|ZEND_ACC_ENUM))) { /* Class declaration must not extend traits or interfaces */ - if ((parent_ce->ce_flags & ZEND_ACC_INTERFACE) || (parent_ce->ce_flags & ZEND_ACC_TRAIT)) { + if (parent_ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT) + || ((parent_ce->ce_flags & ZEND_ACC_ENUM) && !(ce->ce_flags & ZEND_ACC_ENUM))) { zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot extend %s %s", - ZSTR_VAL(ce->name), parent_ce->ce_flags & ZEND_ACC_INTERFACE ? "interface" : "trait", ZSTR_VAL(parent_ce->name) + ZSTR_VAL(ce->name), zend_get_object_type_case(parent_ce, false), ZSTR_VAL(parent_ce->name) ); } + /* Class must not extend a final class */ + if (parent_ce->ce_flags & ZEND_ACC_FINAL) { + zend_error_noreturn(E_COMPILE_ERROR, "Class %s cannot extend final class %s", ZSTR_VAL(ce->name), ZSTR_VAL(parent_ce->name)); + } } if (UNEXPECTED((ce->ce_flags & ZEND_ACC_READONLY_CLASS) != (parent_ce->ce_flags & ZEND_ACC_READONLY_CLASS))) { @@ -2806,6 +2806,11 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent if (!traits[i]) { continue; } + if (UNEXPECTED((ce->ce_flags & ZEND_ACC_ENUM) + && zend_hash_num_elements(&traits[i]->properties_info))) { + zend_error_noreturn(E_COMPILE_ERROR, "Enum %s cannot include properties", + ZSTR_VAL(ce->name)); + } ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&traits[i]->properties_info, prop_name, property_info) { uint32_t flags = property_info->flags; diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8bf..b887b05094fcc 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -641,7 +641,7 @@ interface_declaration_statement: enum_declaration_statement: T_ENUM { $$ = CG(zend_lineno); } T_STRING enum_backing_type implements_list backup_doc_comment '{' class_statement_list '}' - { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM|ZEND_ACC_FINAL, $2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); } + { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM, $2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); } ; enum_backing_type: @@ -650,8 +650,8 @@ enum_backing_type: ; enum_case: - T_CASE backup_doc_comment identifier enum_case_expr ';' - { $$ = zend_ast_create(ZEND_AST_ENUM_CASE, $3, $4, ($2 ? zend_ast_create_zval_from_str($2) : NULL), NULL); } + T_CASE backup_doc_comment identifier optional_parameter_list enum_case_expr ';' + { $$ = zend_ast_create(ZEND_AST_ENUM_CASE, $3, $5, ($2 ? zend_ast_create_zval_from_str($2) : NULL), NULL, $4); } ; enum_case_expr: diff --git a/Zend/zend_smart_str.c b/Zend/zend_smart_str.c index ade137a4bb6c3..d1af5e96b1ccf 100644 --- a/Zend/zend_smart_str.c +++ b/Zend/zend_smart_str.c @@ -229,8 +229,10 @@ ZEND_API zend_result ZEND_FASTCALL smart_str_append_zval(smart_str *dest, const smart_str_append_scalar(dest, value, truncate); } else if (Z_TYPE_P(value) == IS_OBJECT && (Z_OBJCE_P(value)->ce_flags & ZEND_ACC_ENUM)) { smart_str_append(dest, Z_OBJCE_P(value)->name); - smart_str_appends(dest, "::"); - smart_str_append(dest, Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(value)))); + if (!Z_OBJCE_P(value)->parent) { + smart_str_appends(dest, "::"); + smart_str_append(dest, Z_STR_P(zend_enum_fetch_case_name(Z_OBJ_P(value)))); + } } else { return FAILURE; } diff --git a/ext/standard/var.c b/ext/standard/var.c index 0d08a0a338309..d7378b1d2b0e6 100644 --- a/ext/standard/var.c +++ b/ext/standard/var.c @@ -162,12 +162,25 @@ PHPAPI void php_var_dump(zval *struc, int level) /* {{{ */ break; case IS_OBJECT: { zend_class_entry *ce = Z_OBJCE_P(struc); + zend_object *zobj = Z_OBJ_P(struc); if (ce->ce_flags & ZEND_ACC_ENUM) { zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); - php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval)); + if (!ce->parent) { + php_printf("%senum(%s::%s)\n", COMMON, ZSTR_VAL(ce->name), Z_STRVAL_P(case_name_zval)); + } else { + uint32_t num_assoc_values = zend_hash_num_elements(&ce->properties_info) - 1; + php_printf("%senum(%s) (%d) {\n", COMMON, ZSTR_VAL(ce->name), num_assoc_values); + zval *val = OBJ_PROP_NUM(zobj, 1); + zval *end = val + num_assoc_values; + while (val != end) { + zend_property_info *prop_info = zend_get_property_info_for_slot(Z_OBJ_P(struc), val); + php_object_property_dump(prop_info, val, 0, prop_info->name, level); + val++; + } + PUTS("}\n"); + } return; } - zend_object *zobj = Z_OBJ_P(struc); uint32_t *guard = zend_get_recursion_guard(zobj); if (ZEND_GUARD_OR_GC_IS_RECURSIVE(guard, DEBUG, zobj)) { PUTS("*RECURSION*\n"); @@ -599,67 +612,81 @@ PHPAPI zend_result php_var_export_ex(zval *struc, int level, smart_str *buf) /* zend_error(E_WARNING, "var_export does not handle circular references"); return SUCCESS; } - ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj); - myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT); if (level > 1) { smart_str_appendc(buf, '\n'); buffer_append_spaces(buf, level - 1); } - zend_class_entry *ce = Z_OBJCE_P(struc); - bool is_enum = ce->ce_flags & ZEND_ACC_ENUM; - - /* stdClass has no __set_state method, but can be casted to */ - if (ce == zend_standard_class_def) { - smart_str_appendl(buf, "(object) array(\n", 16); - } else { + if (ce->ce_flags & ZEND_ACC_ENUM) { smart_str_appendc(buf, '\\'); smart_str_append(buf, ce->name); - if (is_enum) { - zend_object *zobj = Z_OBJ_P(struc); + if (!ce->parent) { zval *case_name_zval = zend_enum_fetch_case_name(zobj); smart_str_appendl(buf, "::", 2); smart_str_append(buf, Z_STR_P(case_name_zval)); } else { - smart_str_appendl(buf, "::__set_state(array(\n", 21); + smart_str_appendc(buf, '('); + uint32_t num_assoc_values = zend_hash_num_elements(&ce->properties_info) - 1; + zval *val = OBJ_PROP_NUM(zobj, 1); + zval *end = val + num_assoc_values; + while (true) { + php_var_export_ex(val, level + 2, buf); + val++; + if (val == end) { + break; + } + smart_str_appendl(buf, ZEND_STRL(", ")); + } + smart_str_appendc(buf, ')'); } + break; + } + + /* stdClass has no __set_state method, but can be casted to */ + if (ce == zend_standard_class_def) { + smart_str_appendl(buf, "(object) array(\n", 16); + } else { + smart_str_appendc(buf, '\\'); + smart_str_append(buf, ce->name); + smart_str_appendl(buf, "::__set_state(array(\n", 21); } + ZEND_GUARD_OR_GC_PROTECT_RECURSION(guard, EXPORT, zobj); + myht = zend_get_properties_for(struc, ZEND_PROP_PURPOSE_VAR_EXPORT); + if (myht) { - if (!is_enum) { - ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, val) { - /* data is IS_PTR for properties with hooks. */ - zval tmp; - if (UNEXPECTED(Z_TYPE_P(val) == IS_PTR)) { - zend_property_info *prop_info = Z_PTR_P(val); - if ((prop_info->flags & ZEND_ACC_VIRTUAL) && !prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { - continue; - } - const char *unmangled_name_cstr = zend_get_unmangled_property_name(prop_info->name); - zend_string *unmangled_name = zend_string_init(unmangled_name_cstr, strlen(unmangled_name_cstr), false); - val = zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &tmp); - zend_string_release_ex(unmangled_name, false); - if (EG(exception)) { - ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); - zend_release_properties(myht); - return FAILURE; - } + ZEND_HASH_FOREACH_KEY_VAL_IND(myht, index, key, val) { + /* data is IS_PTR for properties with hooks. */ + zval tmp; + if (UNEXPECTED(Z_TYPE_P(val) == IS_PTR)) { + zend_property_info *prop_info = Z_PTR_P(val); + if ((prop_info->flags & ZEND_ACC_VIRTUAL) && !prop_info->hooks[ZEND_PROPERTY_HOOK_GET]) { + continue; } - php_object_element_export(val, index, key, level, buf); - if (val == &tmp) { - zval_ptr_dtor(val); + const char *unmangled_name_cstr = zend_get_unmangled_property_name(prop_info->name); + zend_string *unmangled_name = zend_string_init(unmangled_name_cstr, strlen(unmangled_name_cstr), false); + val = zend_read_property_ex(prop_info->ce, zobj, unmangled_name, /* silent */ true, &tmp); + zend_string_release_ex(unmangled_name, false); + if (EG(exception)) { + ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); + zend_release_properties(myht); + return FAILURE; } - } ZEND_HASH_FOREACH_END(); - } + } + php_object_element_export(val, index, key, level, buf); + if (val == &tmp) { + zval_ptr_dtor(val); + } + } ZEND_HASH_FOREACH_END(); zend_release_properties(myht); } ZEND_GUARD_OR_GC_UNPROTECT_RECURSION(guard, EXPORT, zobj); - if (level > 1 && !is_enum) { + if (level > 1) { buffer_append_spaces(buf, level - 1); } if (ce == zend_standard_class_def) { smart_str_appendc(buf, ')'); - } else if (!is_enum) { + } else { smart_str_appendl(buf, "))", 2); } @@ -1106,6 +1133,7 @@ static void php_var_serialize_intern(smart_str *buf, zval *struc, php_serialize_ } if (ce->ce_flags & ZEND_ACC_ENUM) { + // FIXME PHP_CLASS_ATTRIBUTES; zval *case_name_zval = zend_enum_fetch_case_name(Z_OBJ_P(struc)); From 2b7aed49c47969b052cc7a6325cfcc8c8b349da3 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 5 Sep 2024 18:57:58 +0200 Subject: [PATCH 2/6] Implement ADT ast dumping --- Zend/tests/enum/adt/ast-dumper.phpt | 23 +++++++++++++++++++++++ Zend/zend_ast.c | 5 +++++ 2 files changed, 28 insertions(+) create mode 100644 Zend/tests/enum/adt/ast-dumper.phpt diff --git a/Zend/tests/enum/adt/ast-dumper.phpt b/Zend/tests/enum/adt/ast-dumper.phpt new file mode 100644 index 0000000000000..e8cf95996c721 --- /dev/null +++ b/Zend/tests/enum/adt/ast-dumper.phpt @@ -0,0 +1,23 @@ +--TEST-- +ADT AST dumper +--FILE-- +getMessage(); +} + +?> +--EXPECT-- +assert(false && function () { + enum Foo { + case Bar($x, int $y); + } + +}) diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index d33747412ef0a..c8a0efa2bcd14 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -2577,6 +2577,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio } smart_str_appends(str, "case "); zend_ast_export_name(str, ast->child[0], 0, indent); + if (ast->child[4]) { + smart_str_appendc(str, '('); + zend_ast_export_ex(str, ast->child[4], 0, indent); + smart_str_appendc(str, ')'); + } if (ast->child[1]) { smart_str_appends(str, " = "); zend_ast_export_ex(str, ast->child[1], 0, indent); From 005835c402ef37263b4e75baea46bec3fd95dff6 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 5 Sep 2024 19:13:45 +0200 Subject: [PATCH 3/6] Basic instanceof support Currently, $o instanceof Option::Some is just treated as $o instanceof ('Option::Some'), which has some obvious limitations. It cannot support static, which is only really useful for traits. More significantly, it also cannot support $o instanceof Option::None, because we don't generate classes for singleton cases. --- Zend/tests/enum/adt/instanceof.phpt | 21 +++++++++++++++++++++ Zend/zend_language_parser.y | 13 +++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Zend/tests/enum/adt/instanceof.phpt diff --git a/Zend/tests/enum/adt/instanceof.phpt b/Zend/tests/enum/adt/instanceof.phpt new file mode 100644 index 0000000000000..24a71ebaed8a7 --- /dev/null +++ b/Zend/tests/enum/adt/instanceof.phpt @@ -0,0 +1,21 @@ +--TEST-- +ADT instanceof +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(false) +bool(true) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b887b05094fcc..b0572730e57f8 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -1290,6 +1290,19 @@ expr: { $$ = zend_ast_create_binary_op(ZEND_SPACESHIP, $1, $3); } | expr T_INSTANCEOF class_name_reference { $$ = zend_ast_create(ZEND_AST_INSTANCEOF, $1, $3); } + | expr T_INSTANCEOF class_name T_PAAMAYIM_NEKUDOTAYIM identifier { + // FIXME: Handle static/self + zend_string *enum_name = zend_ast_get_str($3); + zend_string *case_name = zend_ast_get_str($5); + zend_string *adt_name = zend_string_concat3( + ZSTR_VAL(enum_name), ZSTR_LEN(enum_name), + ZEND_STRL("::"), + ZSTR_VAL(case_name), ZSTR_LEN(case_name) + ); + zend_string_release(enum_name); + zend_string_release(case_name); + $$ = zend_ast_create(ZEND_AST_INSTANCEOF, $1, zend_ast_create_zval_from_str(adt_name)); + } | '(' expr ')' { $$ = $2; if ($$->kind == ZEND_AST_CONDITIONAL) $$->attr = ZEND_PARENTHESIZED_CONDITIONAL; From 3f84503bc12d886735e3600b15a51df7ca43eb33 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 5 Sep 2024 19:19:14 +0200 Subject: [PATCH 4/6] Fix test --- Zend/tests/enum/adt/ctor_ref.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/tests/enum/adt/ctor_ref.phpt b/Zend/tests/enum/adt/ctor_ref.phpt index 35387d591b7d8..231e07d9a1cb2 100644 --- a/Zend/tests/enum/adt/ctor_ref.phpt +++ b/Zend/tests/enum/adt/ctor_ref.phpt @@ -4,9 +4,9 @@ ADT assoc value must not be pass-by-reference --EXPECTF-- -Fatal error: Associated value $x of enum case E::A must not be promoted in %s on line %d +Fatal error: Associated value $x of enum case E::A must not be pass-by-reference in %s on line %d From 0d9d29b29ae98c6cf4f4dc609363b28117a20c1d Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Thu, 5 Sep 2024 22:31:42 +0200 Subject: [PATCH 5/6] Implement identity semantics --- Zend/tests/enum/adt/identity.phpt | 26 ++++++++++++++++++++++++++ Zend/zend_compile.c | 3 +++ Zend/zend_operators.c | 27 ++++++++++++++++++++++++++- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/enum/adt/identity.phpt diff --git a/Zend/tests/enum/adt/identity.phpt b/Zend/tests/enum/adt/identity.phpt new file mode 100644 index 0000000000000..96244308db8e3 --- /dev/null +++ b/Zend/tests/enum/adt/identity.phpt @@ -0,0 +1,26 @@ +--TEST-- +ADT identity +--FILE-- + +--EXPECT-- +bool(true) +bool(false) +bool(true) +bool(false) +bool(false) +bool(false) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index f616bae7ff7de..e9d0ee442a352 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9183,6 +9183,9 @@ static zend_class_entry *zend_compile_class_decl(znode *result, zend_ast *ast, b zend_compile_enum_assoc_cases(ce, decl, toplevel); } else { ce->ce_flags |= ZEND_ACC_FINAL; + if (ce->parent_name) { + ce->enum_flags |= ZEND_ENUM_FLAGS_ADT; + } } } diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index ae75e95a71c1d..f9d70173d64ae 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -29,6 +29,7 @@ #include "zend_strtod.h" #include "zend_exceptions.h" #include "zend_closures.h" +#include "zend_enum.h" #include #ifdef HAVE_LANGINFO_H @@ -2384,6 +2385,23 @@ static int hash_zval_identical_function(zval *z1, zval *z2) /* {{{ */ } /* }}} */ +static zend_always_inline bool zend_is_adt_identical(const zend_object *obj1, const zend_object *obj2) +{ + for (int i = 1; i < obj1->ce->default_properties_count; i++) { + zend_property_info *info = obj1->ce->properties_info_table[i]; + if (!info) { + continue; + } + + zval *p1 = OBJ_PROP(obj1, info->offset); + zval *p2 = OBJ_PROP(obj2, info->offset); + if (!zend_is_identical(p1, p2)) { + return false; + } + } + return true; +} + ZEND_API bool ZEND_FASTCALL zend_is_identical(const zval *op1, const zval *op2) /* {{{ */ { if (Z_TYPE_P(op1) != Z_TYPE_P(op2)) { @@ -2406,7 +2424,14 @@ ZEND_API bool ZEND_FASTCALL zend_is_identical(const zval *op1, const zval *op2) return (Z_ARRVAL_P(op1) == Z_ARRVAL_P(op2) || zend_hash_compare(Z_ARRVAL_P(op1), Z_ARRVAL_P(op2), (compare_func_t) hash_zval_identical_function, 1) == 0); case IS_OBJECT: - return (Z_OBJ_P(op1) == Z_OBJ_P(op2)); + if (Z_OBJ_P(op1) == Z_OBJ_P(op2)) { + return true; + } + if (UNEXPECTED(Z_OBJCE_P(op1)->enum_flags & ZEND_ENUM_FLAGS_ADT) + && Z_OBJCE_P(op1) == Z_OBJCE_P(op2)) { + return zend_is_adt_identical(Z_OBJ_P(op1), Z_OBJ_P(op2)); + } + return false; default: return 0; } From 5b206c9d824c0dab9a98c5ee50581a74d12b475e Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 8 Sep 2024 02:14:48 +0200 Subject: [PATCH 6/6] Implement opcache --- Zend/zend_compile.c | 27 +++++++++++++------- Zend/zend_compile.h | 2 ++ Zend/zend_inheritance.c | 44 ++++++++++++++++++--------------- ext/opcache/zend_persist.c | 25 +++++++++++++++++++ ext/opcache/zend_persist_calc.c | 19 ++++++++++++++ 5 files changed, 88 insertions(+), 29 deletions(-) diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index e9d0ee442a352..9875caa01a3bc 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9274,7 +9274,16 @@ static ZEND_NAMED_FUNCTION(zend_enum_adt_constructor) RETURN_THROWS(); } - zend_class_entry *case_ce = EX(func)->internal_function.reserved[0]; + zend_string *case_name = zend_string_concat3( + ZSTR_VAL(func->scope->name), ZSTR_LEN(func->scope->name), + ZEND_STRL("::"), + ZSTR_VAL(func->function_name), ZSTR_LEN(func->function_name) + ); + zend_class_entry *case_ce = zend_lookup_class(case_name); + ZEND_ASSERT(case_ce); + zend_string_release(case_name); + // FIXME: Cache + zend_object *zobj = zend_objects_new(case_ce); zval *prop = OBJ_PROP_NUM(zobj, 0); @@ -9318,13 +9327,16 @@ static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_de } zend_string *case_name = zval_make_interned_string(zend_ast_get_zval(ast->child[0])); + if (!IS_INTERNED(case_name)) { + case_name = zend_string_dup(case_name, true); + } zend_string *case_ce_name = zend_new_interned_string(zend_string_concat3( ZSTR_VAL(enum_ce->name), ZSTR_LEN(enum_ce->name), ZEND_STRL("::"), ZSTR_VAL(case_name), ZSTR_LEN(case_name))); - zend_ast *parent_name_ast = zend_ast_create_zval_from_str(enum_ce->name); + zend_ast *parent_name_ast = zend_ast_create_zval_from_str(zend_string_copy(enum_ce->name)); parent_name_ast->attr = ZEND_NAME_FQ; zend_ast *stmt_list = zend_ast_create_list(0, ZEND_AST_STMT_LIST); @@ -9333,6 +9345,7 @@ static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_de case_ce_name, parent_name_ast, NULL, stmt_list, NULL, NULL); zend_class_entry *case_ce = zend_compile_class_decl(NULL, case_ast_decl, toplevel); + zend_ast_destroy(case_ast_decl); zval default_value; ZVAL_UNDEF(&default_value); @@ -9377,18 +9390,16 @@ static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_de ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, /* doc_comment: FIXME */ NULL, param_type); - // FIXME: This pointer is volatile with opcache... arg_info->name = (const char *) ZSTR_VAL(param_name); arg_info->type = param_type; arg_info->default_value = NULL; - arg_info++; } - const uint32_t fn_flags = ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED; + const uint32_t fn_flags = ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED|ZEND_ACC_ARENA_ARG_INFO; zend_internal_function *func = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1); func->handler = zend_enum_adt_constructor; - func->function_name = zend_string_copy(case_name); + func->function_name = case_name; func->fn_flags = fn_flags; func->doc_comment = NULL; @@ -9401,9 +9412,6 @@ static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_de func->scope = enum_ce; func->T = ZEND_OBSERVER_ENABLED; - /* FIXME: Volatile pointer. */ - func->reserved[0] = case_ce; - ZEND_MAP_PTR_NEW(func->run_time_cache); //if (EG(active)) { // at run-time // ZEND_UNREACHABLE(); @@ -9416,6 +9424,7 @@ static void zend_compile_enum_assoc_cases(zend_class_entry *enum_ce, zend_ast_de if (!zend_hash_add_ptr(&enum_ce->function_table, case_name_lc, func)) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s()", ZSTR_VAL(case_ce_name)); } + zend_string_release(case_name_lc); } } diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0f7c39676c614..2e20c07f221a3 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -395,6 +395,8 @@ typedef struct _zend_oparray_context { /* has #[\Override] attribute | | | */ #define ZEND_ACC_OVERRIDE (1 << 28) /* | X | | */ /* | | | */ +#define ZEND_ACC_ARENA_ARG_INFO (1 << 29) /* | X | | */ +/* | | | */ /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 0c6393d74bcd0..9a0debb26b2d4 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3263,24 +3263,28 @@ static void check_unrecoverable_load_failure(zend_class_entry *ce) { } #define zend_update_inherited_handler(handler) do { \ - if (ce->handler == (zend_function*)op_array) { \ - ce->handler = (zend_function*)new_op_array; \ + if (ce->handler == func) { \ + ce->handler = new_func; \ } \ } while (0) -static zend_op_array *zend_lazy_method_load( - zend_op_array *op_array, zend_class_entry *ce, zend_class_entry *pce) { - ZEND_ASSERT(op_array->type == ZEND_USER_FUNCTION); - ZEND_ASSERT(op_array->scope == pce); - ZEND_ASSERT(op_array->prototype == NULL); - zend_op_array *new_op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); - memcpy(new_op_array, op_array, sizeof(zend_op_array)); - new_op_array->fn_flags &= ~ZEND_ACC_IMMUTABLE; - new_op_array->scope = ce; - ZEND_MAP_PTR_INIT(new_op_array->run_time_cache, NULL); - ZEND_MAP_PTR_INIT(new_op_array->static_variables_ptr, NULL); - - return new_op_array; +static zend_function *zend_lazy_method_load( + zend_function *func, zend_class_entry *ce, zend_class_entry *pce) { + ZEND_ASSERT(func->common.scope == pce); + ZEND_ASSERT(func->common.prototype == NULL); + zend_function *new_func; + if (func->type == ZEND_USER_FUNCTION) { + new_func = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); + memcpy(new_func, func, sizeof(zend_op_array)); + ZEND_MAP_PTR_INIT(new_func->op_array.static_variables_ptr, NULL); + } else { + new_func = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); + memcpy(new_func, func, sizeof(zend_internal_function)); + } + new_func->common.fn_flags &= ~ZEND_ACC_IMMUTABLE; + new_func->common.scope = ce; + ZEND_MAP_PTR_INIT(new_func->common.run_time_cache, NULL); + return new_func; } static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) @@ -3320,8 +3324,8 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) p = ce->function_table.arData; end = p + ce->function_table.nNumUsed; for (; p != end; p++) { - zend_op_array *op_array = Z_PTR(p->val); - zend_op_array *new_op_array = Z_PTR(p->val) = zend_lazy_method_load(op_array, ce, pce); + zend_function *func = Z_PTR(p->val); + zend_function *new_func = Z_PTR(p->val) = zend_lazy_method_load(func, ce, pce); zend_update_inherited_handler(constructor); zend_update_inherited_handler(destructor); @@ -3377,9 +3381,9 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) memcpy(new_prop_info->hooks, prop_info->hooks, ZEND_PROPERTY_HOOK_STRUCT_SIZE); for (uint32_t i = 0; i < ZEND_PROPERTY_HOOK_COUNT; i++) { if (new_prop_info->hooks[i]) { - zend_op_array *hook = zend_lazy_method_load((zend_op_array *) new_prop_info->hooks[i], ce, pce); - ZEND_ASSERT(hook->prop_info == prop_info); - hook->prop_info = new_prop_info; + zend_function *hook = zend_lazy_method_load(new_prop_info->hooks[i], ce, pce); + ZEND_ASSERT(hook->common.prop_info == prop_info); + hook->common.prop_info = new_prop_info; new_prop_info->ce = ce; new_prop_info->hooks[i] = (zend_function *) hook; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index c5ddc040b22d8..f60335a546201 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -732,6 +732,31 @@ static zend_op_array *zend_persist_class_method(zend_op_array *op_array, zend_cl } } } + + if (op_array->fn_flags & ZEND_ACC_ARENA_ARG_INFO) { + ZEND_ASSERT(op_array->arg_info); + zend_internal_arg_info *arg_info = (zend_internal_arg_info *)op_array->arg_info; + uint32_t num_args = op_array->num_args; + ZEND_ASSERT(!(op_array->fn_flags & ZEND_ACC_VARIADIC)); + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + arg_info--; + num_args++; + } + arg_info = zend_shared_memdup_put(arg_info, sizeof(zend_internal_arg_info) * num_args); + for (uint32_t i = 0; i < num_args; i++) { + if (arg_info[i].name) { + zend_string *arg_name = zend_string_init(arg_info[i].name, strlen(arg_info[i].name), false); + arg_name = accel_new_interned_string(arg_name); + zend_accel_store_interned_string(arg_name); + arg_info[i].name = ZSTR_VAL(arg_name); + } + zend_persist_type(&arg_info[i].type); + } + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + arg_info++; + } + op_array->arg_info = (zend_arg_info *)arg_info; + } // Real dynamically created internal functions like enum methods must have their own run_time_cache pointer. They're always on the same scope as their defining class. // However, copies - as caused by inheritance of internal methods - must retain the original run_time_cache pointer, shared with the source function. if (!op_array->scope || (op_array->scope == ce && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE))) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index d7a0455e3e5c1..d80e7935df766 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -340,6 +340,25 @@ static void zend_persist_class_method_calc(zend_op_array *op_array) if (!old_op_array) { ADD_SIZE(sizeof(zend_internal_function)); zend_shared_alloc_register_xlat_entry(op_array, op_array); + + if (op_array->fn_flags & ZEND_ACC_ARENA_ARG_INFO) { + ZEND_ASSERT(op_array->arg_info); + zend_internal_arg_info *arg_info = (zend_internal_arg_info *)op_array->arg_info; + uint32_t num_args = op_array->num_args; + ZEND_ASSERT(!(op_array->fn_flags & ZEND_ACC_VARIADIC)); + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + arg_info--; + num_args++; + } + ADD_SIZE(sizeof(zend_internal_arg_info) * num_args); + for (uint32_t i = 0; i < num_args; i++) { + if (arg_info[i].name) { + zend_string *arg_name = zend_string_init(arg_info[i].name, strlen(arg_info[i].name), false); + ADD_INTERNED_STRING(arg_name); + } + zend_persist_type_calc(&arg_info[i].type); + } + } } } return;