From 3b1dfb674fcfdfb901a07611c70aad858ee59fce Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sat, 18 Jun 2022 23:12:01 +0200 Subject: [PATCH 1/3] Auto-implement Stringable for string backed enums --- Zend/tests/enum/backed-stringable.phpt | 26 ++++++++++++++ Zend/zend_enum.c | 47 +++++++++++++++++++++++++- Zend/zend_string.h | 2 ++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 Zend/tests/enum/backed-stringable.phpt diff --git a/Zend/tests/enum/backed-stringable.phpt b/Zend/tests/enum/backed-stringable.phpt new file mode 100644 index 0000000000000..45c193119cee7 --- /dev/null +++ b/Zend/tests/enum/backed-stringable.phpt @@ -0,0 +1,26 @@ +--TEST-- +Backed string enum auto-implement Stringable +--FILE-- + +--EXPECT-- +Baz +Baz +Baz +Baz +bool(true) diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index a4a450d22fba6..7be075db67813 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -20,6 +20,7 @@ #include "zend_API.h" #include "zend_compile.h" #include "zend_enum_arginfo.h" +#include "zend_interfaces_arginfo.h" #include "zend_interfaces.h" #include "zend_enum.h" @@ -81,11 +82,14 @@ static void zend_verify_enum_magic_methods(zend_class_entry *ce) ZEND_ENUM_DISALLOW_MAGIC_METHOD(__set, "__set"); ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unset, "__unset"); ZEND_ENUM_DISALLOW_MAGIC_METHOD(__isset, "__isset"); - ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString"); ZEND_ENUM_DISALLOW_MAGIC_METHOD(__debugInfo, "__debugInfo"); ZEND_ENUM_DISALLOW_MAGIC_METHOD(__serialize, "__serialize"); ZEND_ENUM_DISALLOW_MAGIC_METHOD(__unserialize, "__unserialize"); + if (ce->enum_backing_type != IS_STRING) { + ZEND_ENUM_DISALLOW_MAGIC_METHOD(__tostring, "__toString"); + } + const char *forbidden_methods[] = { "__sleep", "__wakeup", @@ -169,6 +173,10 @@ void zend_enum_add_interfaces(zend_class_entry *ce) ce->num_interfaces++; if (ce->enum_backing_type != IS_UNDEF) { ce->num_interfaces++; + + if (ce->enum_backing_type == IS_STRING) { + ce->num_interfaces++; + } } ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)); @@ -181,6 +189,11 @@ void zend_enum_add_interfaces(zend_class_entry *ce) if (ce->enum_backing_type != IS_UNDEF) { 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 = zend_string_init("backedenum", sizeof("backedenum") - 1, 0); + + if (ce->enum_backing_type == IS_STRING) { + ce->interface_names[num_interfaces_before + 2].name = zend_string_copy(zend_ce_stringable->name); + ce->interface_names[num_interfaces_before + 2].lc_name = zend_string_init("stringable", sizeof("stringable") - 1, 0); + } } } @@ -394,6 +407,16 @@ static ZEND_NAMED_FUNCTION(zend_enum_try_from_func) zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1); } +static ZEND_NAMED_FUNCTION(zend_enum_tostring_func) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zval *value = zend_enum_fetch_case_value(Z_OBJ_P(ZEND_THIS)); + ZEND_ASSERT(Z_TYPE_P(value) == IS_STRING); + + RETURN_COPY(value); +} + void zend_enum_register_funcs(zend_class_entry *ce) { const uint32_t fn_flags = @@ -447,6 +470,28 @@ void zend_enum_register_funcs(zend_class_entry *ce) zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::tryFrom()", ZSTR_VAL(ce->name)); } + + if (ce->enum_backing_type == IS_STRING) { + zend_internal_function *tostring_function = + zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); + memset(tostring_function, 0, sizeof(zend_internal_function)); + tostring_function->type = ZEND_INTERNAL_FUNCTION; + tostring_function->module = EG(current_module); + tostring_function->handler = zend_enum_tostring_func; + tostring_function->function_name = ZSTR_KNOWN(ZEND_STR_TOSTRING); + tostring_function->scope = ce; + tostring_function->fn_flags = ZEND_ACC_PUBLIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED; + tostring_function->num_args = 0; + tostring_function->required_num_args = 0; + tostring_function->arg_info = (zend_internal_arg_info *) (arginfo_class_Stringable___toString + 1); + if (!zend_hash_add_ptr( + &ce->function_table, ZSTR_KNOWN(ZEND_STR_TOSTRING_LOWERCASE), tostring_function)) { + zend_error_noreturn(E_COMPILE_ERROR, + "Cannot redeclare %s::__toString()", ZSTR_VAL(ce->name)); + } + + ce->__tostring = (zend_function *) tostring_function; + } } } diff --git a/Zend/zend_string.h b/Zend/zend_string.h index ef67daad74bbf..ef5309516952b 100644 --- a/Zend/zend_string.h +++ b/Zend/zend_string.h @@ -592,6 +592,8 @@ EMPTY_SWITCH_DEFAULT_CASE() _(ZEND_STR_AUTOGLOBAL_ENV, "_ENV") \ _(ZEND_STR_AUTOGLOBAL_REQUEST, "_REQUEST") \ _(ZEND_STR_COUNT, "count") \ + _(ZEND_STR_TOSTRING, "__toString") \ + _(ZEND_STR_TOSTRING_LOWERCASE, "__tostring") \ typedef enum _zend_known_string_id { From 3b5603b79dd0a1c074c4189a6e94fa0a4a494d72 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 19 Jun 2022 20:40:23 +0200 Subject: [PATCH 2/3] Add more test cases --- Zend/tests/enum/__toString.phpt | 17 +++++++++++++++++ Zend/tests/enum/backed-string__toString.phpt | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 Zend/tests/enum/__toString.phpt create mode 100644 Zend/tests/enum/backed-string__toString.phpt diff --git a/Zend/tests/enum/__toString.phpt b/Zend/tests/enum/__toString.phpt new file mode 100644 index 0000000000000..e9831a78eaea5 --- /dev/null +++ b/Zend/tests/enum/__toString.phpt @@ -0,0 +1,17 @@ +--TEST-- +Enum __toString +--FILE-- + +--EXPECTF-- +Fatal error: Enum may not include __toString in %s on line %d diff --git a/Zend/tests/enum/backed-string__toString.phpt b/Zend/tests/enum/backed-string__toString.phpt new file mode 100644 index 0000000000000..c56f4040067d3 --- /dev/null +++ b/Zend/tests/enum/backed-string__toString.phpt @@ -0,0 +1,17 @@ +--TEST-- +Backed string enum __toString +--FILE-- + +--EXPECTF-- +Fatal error: Cannot redeclare Foo::__toString() in %s on line %d From 8532d266e97545bc8cb919dcade33cca40573b73 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sun, 19 Jun 2022 21:02:28 +0200 Subject: [PATCH 3/3] More tests --- Zend/tests/enum/backed-int-__toString.phpt | 17 +++++++++++++++++ ...tring.phpt => backed-string-__toString.phpt} | 0 2 files changed, 17 insertions(+) create mode 100644 Zend/tests/enum/backed-int-__toString.phpt rename Zend/tests/enum/{backed-string__toString.phpt => backed-string-__toString.phpt} (100%) diff --git a/Zend/tests/enum/backed-int-__toString.phpt b/Zend/tests/enum/backed-int-__toString.phpt new file mode 100644 index 0000000000000..ca71678e10b2b --- /dev/null +++ b/Zend/tests/enum/backed-int-__toString.phpt @@ -0,0 +1,17 @@ +--TEST-- +Backed int enum __toString is forbidden +--FILE-- + +--EXPECTF-- +Fatal error: Enum may not include __toString in %s on line %d diff --git a/Zend/tests/enum/backed-string__toString.phpt b/Zend/tests/enum/backed-string-__toString.phpt similarity index 100% rename from Zend/tests/enum/backed-string__toString.phpt rename to Zend/tests/enum/backed-string-__toString.phpt