From 4358a0591caa3cfda241c891aa39ca62917a2911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sat, 28 Dec 2024 22:11:00 +0200 Subject: [PATCH 01/25] Add syntax for optional interfaces --- Zend/zend.h | 8 +++++++- Zend/zend_compile.c | 8 +++++--- Zend/zend_compile.h | 2 ++ Zend/zend_enum.c | 4 +++- Zend/zend_inheritance.c | 13 +++++++++++-- Zend/zend_language_parser.y | 23 +++++++++++++++++------ ext/opcache/zend_file_cache.c | 3 ++- ext/opcache/zend_persist.c | 3 ++- ext/opcache/zend_persist_calc.c | 2 +- 9 files changed, 50 insertions(+), 16 deletions(-) mode change 100644 => 100755 Zend/zend.h mode change 100644 => 100755 ext/opcache/zend_file_cache.c diff --git a/Zend/zend.h b/Zend/zend.h old mode 100644 new mode 100755 index 0cf1faeb653fe..79a6127d3efc7 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -87,6 +87,12 @@ typedef struct _zend_class_name { zend_string *lc_name; } zend_class_name; +typedef struct _zend_interface_name { + zend_string *name; + zend_string *lc_name; + bool is_optional; +} zend_interface_name; + typedef struct _zend_trait_method_reference { zend_string *method_name; zend_string *class_name; @@ -210,7 +216,7 @@ struct _zend_class_entry { /* class_entry or string(s) depending on ZEND_ACC_LINKED */ union { zend_class_entry **interfaces; - zend_class_name *interface_names; + zend_interface_name *interface_names; }; zend_class_name *trait_names; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b4dea55414ffc..001f8ca3c177f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8061,12 +8061,13 @@ static void add_stringable_interface(zend_class_entry *ce) { ce->num_interfaces++; ce->interface_names = - erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + erealloc(ce->interface_names, sizeof(zend_interface_name) * ce->num_interfaces); // TODO: Add known interned strings instead? ce->interface_names[ce->num_interfaces - 1].name = ZSTR_INIT_LITERAL("Stringable", 0); ce->interface_names[ce->num_interfaces - 1].lc_name = ZSTR_INIT_LITERAL("stringable", 0); + ce->interface_names[ce->num_interfaces - 1].is_optional = false; } static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string *name, bool has_body) /* {{{ */ @@ -8977,16 +8978,17 @@ static void zend_compile_implements(zend_ast *ast) /* {{{ */ { zend_ast_list *list = zend_ast_get_list(ast); zend_class_entry *ce = CG(active_class_entry); - zend_class_name *interface_names; + zend_interface_name *interface_names; uint32_t i; - interface_names = emalloc(sizeof(zend_class_name) * list->children); + interface_names = emalloc(sizeof(zend_interface_name) * list->children); for (i = 0; i < list->children; ++i) { zend_ast *class_ast = list->child[i]; interface_names[i].name = zend_resolve_const_class_name_reference(class_ast, "interface name"); interface_names[i].lc_name = zend_string_tolower(interface_names[i].name); + interface_names[i].is_optional = ZEND_CLASS_NAME_OPTIONAL & class_ast->attr; } ce->num_interfaces = list->children; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 63572ab6623cc..f9d092f8f81af 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1035,6 +1035,8 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_NAME_NOT_FQ 1 #define ZEND_NAME_RELATIVE 2 +#define ZEND_CLASS_NAME_OPTIONAL 4 + /* ZEND_FETCH_ flags in class name AST of new const expression must not clash with ZEND_NAME_ flags */ #define ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT 2 diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 8259441fd4228..13669b9c80f2d 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -180,14 +180,16 @@ void zend_enum_add_interfaces(zend_class_entry *ce) ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES)); - ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + ce->interface_names = erealloc(ce->interface_names, sizeof(zend_interface_name) * ce->num_interfaces); ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_unit_enum->name); ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("unitenum", 0); + ce->interface_names[num_interfaces_before].is_optional = false; 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 = ZSTR_INIT_LITERAL("backedenum", 0); + ce->interface_names[num_interfaces_before + 1].is_optional = false; } ce->default_object_handlers = &zend_enum_object_handlers; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 23806af57f693..c7f4541391ba1 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3527,23 +3527,32 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string } } + size_t num_implementable_interfaces = 0; if (ce->num_interfaces) { for (i = 0; i < ce->num_interfaces; i++) { zend_class_entry *iface = zend_fetch_class_by_name( ce->interface_names[i].name, ce->interface_names[i].lc_name, ZEND_FETCH_CLASS_INTERFACE | - ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION); + ZEND_FETCH_CLASS_ALLOW_NEARLY_LINKED | ZEND_FETCH_CLASS_EXCEPTION | + (ce->interface_names[i].is_optional ? ZEND_FETCH_CLASS_SILENT : 0)); + + // Optional interfaces are skipped if they don't exist. + if (!iface && ce->interface_names[i].is_optional) { + continue; + } + if (!iface) { check_unrecoverable_load_failure(ce); free_alloca(traits_and_interfaces, use_heap); return NULL; } - traits_and_interfaces[ce->num_traits + i] = iface; + traits_and_interfaces[ce->num_traits + num_implementable_interfaces++] = iface; if (iface) { UPDATE_IS_CACHEABLE(iface); } } } + ce->num_interfaces = num_implementable_interfaces; #ifndef ZEND_WIN32 if (ce->ce_flags & ZEND_ACC_ENUM) { diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8bf..bcdde0496607c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -285,6 +285,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %type function_name non_empty_member_modifiers %type property_hook property_hook_list optional_property_hook_list hooked_property property_hook_body %type optional_parameter_list +%type interface_name_list interface_name %type returns_ref function fn is_reference is_variadic property_modifiers property_hook_modifiers %type method_modifiers class_const_modifiers member_modifier optional_cpp_modifiers @@ -355,10 +356,10 @@ legacy_namespace_name: ; name: - T_STRING { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; } - | T_NAME_QUALIFIED { $$ = $1; $$->attr = ZEND_NAME_NOT_FQ; } - | T_NAME_FULLY_QUALIFIED { $$ = $1; $$->attr = ZEND_NAME_FQ; } - | T_NAME_RELATIVE { $$ = $1; $$->attr = ZEND_NAME_RELATIVE; } + T_STRING { $$ = $1; $$->attr |= ZEND_NAME_NOT_FQ; } + | T_NAME_QUALIFIED { $$ = $1; $$->attr |= ZEND_NAME_NOT_FQ; } + | T_NAME_FULLY_QUALIFIED { $$ = $1; $$->attr |= ZEND_NAME_FQ; } + | T_NAME_RELATIVE { $$ = $1; $$->attr |= ZEND_NAME_RELATIVE; } ; attribute_decl: @@ -666,12 +667,12 @@ extends_from: interface_extends_list: %empty { $$ = NULL; } - | T_EXTENDS class_name_list { $$ = $2; } + | T_EXTENDS interface_name_list { $$ = $2; } ; implements_list: %empty { $$ = NULL; } - | T_IMPLEMENTS class_name_list { $$ = $2; } + | T_IMPLEMENTS interface_name_list { $$ = $2; } ; foreach_variable: @@ -974,6 +975,11 @@ class_name_list: | class_name_list ',' class_name { $$ = zend_ast_list_add($1, $3); } ; +interface_name_list: + interface_name { $$ = zend_ast_create_list(1, ZEND_AST_NAME_LIST, $1); } + | interface_name_list ',' interface_name { $$ = zend_ast_list_add($1, $3); } +; + trait_adaptations: ';' { $$ = NULL; } | '{' '}' { $$ = NULL; } @@ -1411,6 +1417,11 @@ class_name: | name { $$ = $1; } ; +interface_name: + class_name { $$ = $1; } + | '?' name { $$ = $2; $$->attr |= ZEND_CLASS_NAME_OPTIONAL; } +; + class_name_reference: class_name { $$ = $1; } | new_variable { $$ = $1; } diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c old mode 100644 new mode 100755 index 2a13f751ce3fa..3fadcfaa6be31 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -795,7 +795,7 @@ static void zend_file_cache_serialize_class(zval *zv, if (ce->num_interfaces) { uint32_t i; - zend_class_name *interface_names; + zend_interface_name *interface_names; ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_LINKED)); @@ -806,6 +806,7 @@ static void zend_file_cache_serialize_class(zval *zv, for (i = 0; i < ce->num_interfaces; i++) { SERIALIZE_STR(interface_names[i].name); SERIALIZE_STR(interface_names[i].lc_name); + // SERIALIZE_BOOL(interface_names[i].is_optional) ? } } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index bf0b3d1988144..17740ae669234 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -1054,8 +1054,9 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) for (i = 0; i < ce->num_interfaces; i++) { zend_accel_store_interned_string(ce->interface_names[i].name); zend_accel_store_interned_string(ce->interface_names[i].lc_name); + // TODO: should we store .is_optional here? How? } - ce->interface_names = zend_shared_memdup_free(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces); + ce->interface_names = zend_shared_memdup_free(ce->interface_names, sizeof(zend_interface_name) * ce->num_interfaces); } if (ce->num_traits) { diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 0c53923354c42..d73f3af375777 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -524,7 +524,7 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ADD_INTERNED_STRING(ce->interface_names[i].name); ADD_INTERNED_STRING(ce->interface_names[i].lc_name); } - ADD_SIZE(sizeof(zend_class_name) * ce->num_interfaces); + ADD_SIZE(sizeof(zend_interface_name) * ce->num_interfaces); } } From a0fe072e9ef0837e0be1d85c68d1712cfeaedd5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 29 Dec 2024 19:33:54 +0200 Subject: [PATCH 02/25] Remove resolved comments --- ext/opcache/zend_file_cache.c | 3 +-- ext/opcache/zend_persist.c | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index 3fadcfaa6be31..cefa266f59d1a 100755 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -806,7 +806,6 @@ static void zend_file_cache_serialize_class(zval *zv, for (i = 0; i < ce->num_interfaces; i++) { SERIALIZE_STR(interface_names[i].name); SERIALIZE_STR(interface_names[i].lc_name); - // SERIALIZE_BOOL(interface_names[i].is_optional) ? } } @@ -2029,7 +2028,7 @@ void zend_file_cache_invalidate(zend_string *full_path) if (ZCG(accel_directives).file_cache_read_only) { return; } - + char *filename; filename = zend_file_cache_get_bin_file_path(full_path); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 17740ae669234..bf5578c5d9360 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -1054,7 +1054,6 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) for (i = 0; i < ce->num_interfaces; i++) { zend_accel_store_interned_string(ce->interface_names[i].name); zend_accel_store_interned_string(ce->interface_names[i].lc_name); - // TODO: should we store .is_optional here? How? } ce->interface_names = zend_shared_memdup_free(ce->interface_names, sizeof(zend_interface_name) * ce->num_interfaces); } From 4ed9176f1e048cfd10a1653cfe7d6a645f85f54c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 29 Dec 2024 19:38:46 +0200 Subject: [PATCH 03/25] Move the `num_interfaces` mutation to a safer place --- Zend/zend_inheritance.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index c7f4541391ba1..a7d673672a436 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3552,7 +3552,6 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string } } } - ce->num_interfaces = num_implementable_interfaces; #ifndef ZEND_WIN32 if (ce->ce_flags & ZEND_ACC_ENUM) { @@ -3598,6 +3597,8 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string Z_CE_P(zv) = ce; } + ce->num_interfaces = num_implementable_interfaces; + if (CG(unlinked_uses)) { zend_hash_index_del(CG(unlinked_uses), (zend_long)(uintptr_t) ce); } From eba9a20a9b3b417fe1fe69c3e1da1a4e94197071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 02:13:06 +0200 Subject: [PATCH 04/25] Test optional interfaces --- .../optional_interfaces/autoloading.phpt | 24 ++++++ .../dynamic_definition.phpt | 41 ++++++++++ Zend/tests/optional_interfaces/opcache.phpt | 33 ++++++++ .../optional_interfaces/opcached_class.inc | 3 + .../optional_interfaces.phpt | 77 +++++++++++++++++++ .../optional_interfaces_are_checked.phpt | 25 ++++++ Zend/tests/optional_interfaces/override.phpt | 22 ++++++ .../tests/optional_interfaces/reflection.phpt | 15 ++++ .../optional_interfaces/serialization.phpt | 16 ++++ .../optional_interfaces/type_checks.phpt | 27 +++++++ 10 files changed, 283 insertions(+) create mode 100644 Zend/tests/optional_interfaces/autoloading.phpt create mode 100644 Zend/tests/optional_interfaces/dynamic_definition.phpt create mode 100644 Zend/tests/optional_interfaces/opcache.phpt create mode 100644 Zend/tests/optional_interfaces/opcached_class.inc create mode 100644 Zend/tests/optional_interfaces/optional_interfaces.phpt create mode 100644 Zend/tests/optional_interfaces/optional_interfaces_are_checked.phpt create mode 100644 Zend/tests/optional_interfaces/override.phpt create mode 100644 Zend/tests/optional_interfaces/reflection.phpt create mode 100644 Zend/tests/optional_interfaces/serialization.phpt create mode 100644 Zend/tests/optional_interfaces/type_checks.phpt diff --git a/Zend/tests/optional_interfaces/autoloading.phpt b/Zend/tests/optional_interfaces/autoloading.phpt new file mode 100644 index 0000000000000..9b2ffd451857a --- /dev/null +++ b/Zend/tests/optional_interfaces/autoloading.phpt @@ -0,0 +1,24 @@ +--TEST-- +Optional interfaces are autoloaded +--FILE-- + +--EXPECT-- +Autoloading: ExistingInterface +Autoloading: NonExistantInterface +ExistingInterface +ExistingInterface diff --git a/Zend/tests/optional_interfaces/dynamic_definition.phpt b/Zend/tests/optional_interfaces/dynamic_definition.phpt new file mode 100644 index 0000000000000..45bacbf46ad3b --- /dev/null +++ b/Zend/tests/optional_interfaces/dynamic_definition.phpt @@ -0,0 +1,41 @@ +--TEST-- +Dynamically defined interfaces only affect classes defined later +--FILE-- + +--EXPECT-- +AC implements: +AC object implements: + +Interface defined + +NC implements: OptionalInterface +NC implements: OptionalInterface + +AC object implements: +AC implements: +New AC object implements: diff --git a/Zend/tests/optional_interfaces/opcache.phpt b/Zend/tests/optional_interfaces/opcache.phpt new file mode 100644 index 0000000000000..a2df1ea0e8741 --- /dev/null +++ b/Zend/tests/optional_interfaces/opcache.phpt @@ -0,0 +1,33 @@ +--TEST-- +Optional interfaces are rechecked on subsequent requests +--INI-- +opcache.enable_cli=1 +opcache.enable=1 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECT-- +OpcachedInterface is not defined +OpcachedClass implements + +OpcachedInterface is defined +OpcachedClass implements OpcachedInterface + +OpcachedInterface is not defined +OpcachedClass implements diff --git a/Zend/tests/optional_interfaces/opcached_class.inc b/Zend/tests/optional_interfaces/opcached_class.inc new file mode 100644 index 0000000000000..a6d906b6e4756 --- /dev/null +++ b/Zend/tests/optional_interfaces/opcached_class.inc @@ -0,0 +1,3 @@ + +--EXPECT-- +ImplementingOptionalInterface + class implements ExistingInterface + object implements ExistingInterface +SkippingOptionalInterface + class implements + object implements +MultipleOptionalsExistingFirst + class implements ExistingInterface + object implements ExistingInterface +MultipleOptionalsExistingLast + class implements ExistingInterface + object implements ExistingInterface +MixedOptionalFirst + class implements ExistingInterface + object implements ExistingInterface +MixedOptionalLast + class implements ExistingInterface + object implements ExistingInterface +ImplementsInheritedExisting + class implements ExtendingExistingOptional, ExistingInterface + object implements ExtendingExistingOptional, ExistingInterface +ImplementsInheritedSkipped + class implements ExtendingNonexistantOptional + object implements ExtendingNonexistantOptional +ExtendingExistingOptional + interface extends ExtendingNonexistantOptional +ExtendingNonexistantOptional + interface extends ExtendingNonexistantOptional diff --git a/Zend/tests/optional_interfaces/optional_interfaces_are_checked.phpt b/Zend/tests/optional_interfaces/optional_interfaces_are_checked.phpt new file mode 100644 index 0000000000000..708ff28509e44 --- /dev/null +++ b/Zend/tests/optional_interfaces/optional_interfaces_are_checked.phpt @@ -0,0 +1,25 @@ +--TEST-- +Optional interfaces are checked +--FILE-- + +--EXPECTF-- +Existing interfaces can be implemented. +Fatal error: Class BadClass contains 1 abstract method and must therefore be declared abstract or implement the remaining method (Iface::method) in %soptional_interfaces_are_checked.php on line %d diff --git a/Zend/tests/optional_interfaces/override.phpt b/Zend/tests/optional_interfaces/override.phpt new file mode 100644 index 0000000000000..248143f74c85a --- /dev/null +++ b/Zend/tests/optional_interfaces/override.phpt @@ -0,0 +1,22 @@ +--TEST-- +Optional interfaces doesn't make Override optional +--FILE-- + +--EXPECTF-- +Fatal error: TestClass::other() has #[\Override] attribute, but no matching parent method exists in %soptional_interfaces/override.php on line %d diff --git a/Zend/tests/optional_interfaces/reflection.phpt b/Zend/tests/optional_interfaces/reflection.phpt new file mode 100644 index 0000000000000..70f7be35481f4 --- /dev/null +++ b/Zend/tests/optional_interfaces/reflection.phpt @@ -0,0 +1,15 @@ +--TEST-- +Reflection works on optional interfaces +--FILE-- +getInterfaceNames()); + +?> +--EXPECT-- +ExistingInterface diff --git a/Zend/tests/optional_interfaces/serialization.phpt b/Zend/tests/optional_interfaces/serialization.phpt new file mode 100644 index 0000000000000..ad60c93cfce9e --- /dev/null +++ b/Zend/tests/optional_interfaces/serialization.phpt @@ -0,0 +1,16 @@ +--TEST-- +Classes with optional interfaces survive serialization +--FILE-- + +--EXPECT-- +ExistingInterface diff --git a/Zend/tests/optional_interfaces/type_checks.phpt b/Zend/tests/optional_interfaces/type_checks.phpt new file mode 100644 index 0000000000000..b701720138148 --- /dev/null +++ b/Zend/tests/optional_interfaces/type_checks.phpt @@ -0,0 +1,27 @@ +--TEST-- +Only the existing interfaces pass the type checks +--INI-- +zend.exception_ignore_args = On +--FILE-- + +--EXPECTF-- +F1 +Fatal error: Uncaught TypeError: f2(): Argument #1 ($x) must be of type NonExistantInterface, TestClass given, called in %stype_checks.php on line %d and defined in %stype_checks.php:%d +Stack trace: +#0 %stype_checks.php(%d): f2(Object(TestClass)) +#1 {main} + thrown in %stype_checks.php on line %d From 1386ca227a0375098b1a30648d139029cc47cf67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 04:56:17 +0200 Subject: [PATCH 05/25] Fix memory leak We need to free interfaces names before we overwrite their counter. --- Zend/zend_inheritance.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index a7d673672a436..80ddbc90cf46d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2232,14 +2232,6 @@ static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry } } - if (!(ce->ce_flags & ZEND_ACC_CACHED)) { - for (i = 0; i < ce->num_interfaces; i++) { - zend_string_release_ex(ce->interface_names[i].name, 0); - zend_string_release_ex(ce->interface_names[i].lc_name, 0); - } - efree(ce->interface_names); - } - ce->num_interfaces = num_interfaces; ce->interfaces = interfaces; ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES; @@ -3597,6 +3589,14 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string Z_CE_P(zv) = ce; } + if (!(ce->ce_flags & ZEND_ACC_CACHED)) { + for (i = 0; i < ce->num_interfaces; i++) { + zend_string_release_ex(ce->interface_names[i].name, 0); + zend_string_release_ex(ce->interface_names[i].lc_name, 0); + } + efree(ce->interface_names); + } + ce->num_interfaces = num_implementable_interfaces; if (CG(unlinked_uses)) { @@ -3642,6 +3642,7 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string } else if (parent && parent->num_interfaces) { zend_do_inherit_interfaces(ce, parent); } + if (!(ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT)) && (ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) ) { From 686d2e9546f5c0dba10edcf51dabfb07ad8ada87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 04:56:38 +0200 Subject: [PATCH 06/25] Add test for Stringable with optional interfaces --- .../tests/optional_interfaces/stringable.phpt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 Zend/tests/optional_interfaces/stringable.phpt diff --git a/Zend/tests/optional_interfaces/stringable.phpt b/Zend/tests/optional_interfaces/stringable.phpt new file mode 100644 index 0000000000000..b100bebe9f6f2 --- /dev/null +++ b/Zend/tests/optional_interfaces/stringable.phpt @@ -0,0 +1,20 @@ +--TEST-- +Adding stringable is not broken with Optional interfaces +--FILE-- + +--EXPECT-- +Stringable From b00e2f370228070b0c99af5f0599020ee9367f6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 04:56:55 +0200 Subject: [PATCH 07/25] Simplify test --- Zend/tests/optional_interfaces/type_checks.phpt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/tests/optional_interfaces/type_checks.phpt b/Zend/tests/optional_interfaces/type_checks.phpt index b701720138148..369779c5b53b9 100644 --- a/Zend/tests/optional_interfaces/type_checks.phpt +++ b/Zend/tests/optional_interfaces/type_checks.phpt @@ -15,13 +15,13 @@ function f2(NonExistantInterface $x) { echo "F2"; } $c = new TestClass; f1($c); -f2($c) +f2($c); ?> --EXPECTF-- F1 Fatal error: Uncaught TypeError: f2(): Argument #1 ($x) must be of type NonExistantInterface, TestClass given, called in %stype_checks.php on line %d and defined in %stype_checks.php:%d Stack trace: -#0 %stype_checks.php(%d): f2(Object(TestClass)) +#0 %s #1 {main} thrown in %stype_checks.php on line %d From 99f0837c78027c42f2b46c4dd8c89314c75c96d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 05:00:55 +0200 Subject: [PATCH 08/25] Make test line-trimming-resilient --- .../dynamic_definition.phpt | 18 ++++---- .../optional_interfaces.phpt | 42 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/Zend/tests/optional_interfaces/dynamic_definition.phpt b/Zend/tests/optional_interfaces/dynamic_definition.phpt index 45bacbf46ad3b..0bf47525b4baf 100644 --- a/Zend/tests/optional_interfaces/dynamic_definition.phpt +++ b/Zend/tests/optional_interfaces/dynamic_definition.phpt @@ -5,8 +5,8 @@ Dynamically defined interfaces only affect classes defined later class AncientClass implements ?OptionalInterface {} $ancientClass = new AncientClass; -echo 'AC implements: '.implode(', ', class_implements('AncientClass')),"\n"; -echo 'AC object implements: '.implode(', ', class_implements($ancientClass)),"\n\n"; +echo 'AC implements:'.implode(', ', class_implements('AncientClass')),"\n"; +echo 'AC object implements:'.implode(', ', class_implements($ancientClass)),"\n\n"; eval('interface OptionalInterface {}'); echo "Interface defined\n\n"; @@ -15,16 +15,16 @@ class NewClass implements ?OptionalInterface {} // Newly defined class implements the interface $newClass = new NewClass; -echo 'NC implements: '.implode(', ', class_implements('NewClass')),"\n"; -echo 'NC implements: '.implode(', ', class_implements($newClass)),"\n\n"; +echo 'NC implements:'.implode(', ', class_implements('NewClass')),"\n"; +echo 'NC implements:'.implode(', ', class_implements($newClass)),"\n\n"; // The old class doesn't -echo 'AC object implements: '.implode(', ', class_implements($ancientClass)),"\n"; +echo 'AC object implements:'.implode(', ', class_implements($ancientClass)),"\n"; -echo 'AC implements: '.implode(', ', class_implements('AncientClass')),"\n"; +echo 'AC implements:'.implode(', ', class_implements('AncientClass')),"\n"; $ancientClass2 = new AncientClass; -echo 'New AC object implements: '.implode(', ', class_implements($ancientClass2)),"\n"; +echo 'New AC object implements:'.implode(', ', class_implements($ancientClass2)),"\n"; ?> --EXPECT-- @@ -33,8 +33,8 @@ AC object implements: Interface defined -NC implements: OptionalInterface -NC implements: OptionalInterface +NC implements:OptionalInterface +NC implements:OptionalInterface AC object implements: AC implements: diff --git a/Zend/tests/optional_interfaces/optional_interfaces.phpt b/Zend/tests/optional_interfaces/optional_interfaces.phpt index 0ea6b246b0268..f3bd78e404bc6 100644 --- a/Zend/tests/optional_interfaces/optional_interfaces.phpt +++ b/Zend/tests/optional_interfaces/optional_interfaces.phpt @@ -31,8 +31,8 @@ $classes = [ foreach ($classes as $class) { echo "$class\n"; - echo ' class implements '.implode(', ', class_implements($class))."\n"; - echo ' object implements '.implode(', ', class_implements(new $class))."\n"; + echo ' class implements:'.implode(', ', class_implements($class))."\n"; + echo ' object implements:'.implode(', ', class_implements(new $class))."\n"; } $interfaces = [ @@ -42,36 +42,36 @@ $interfaces = [ foreach ($interfaces as $interface) { echo "$interface\n"; - echo ' interface extends '.implode(', ', class_implements($class))."\n"; + echo ' interface extends:'.implode(', ', class_implements($class))."\n"; } ?> --EXPECT-- ImplementingOptionalInterface - class implements ExistingInterface - object implements ExistingInterface + class implements:ExistingInterface + object implements:ExistingInterface SkippingOptionalInterface - class implements - object implements + class implements: + object implements: MultipleOptionalsExistingFirst - class implements ExistingInterface - object implements ExistingInterface + class implements:ExistingInterface + object implements:ExistingInterface MultipleOptionalsExistingLast - class implements ExistingInterface - object implements ExistingInterface + class implements:ExistingInterface + object implements:ExistingInterface MixedOptionalFirst - class implements ExistingInterface - object implements ExistingInterface + class implements:ExistingInterface + object implements:ExistingInterface MixedOptionalLast - class implements ExistingInterface - object implements ExistingInterface + class implements:ExistingInterface + object implements:ExistingInterface ImplementsInheritedExisting - class implements ExtendingExistingOptional, ExistingInterface - object implements ExtendingExistingOptional, ExistingInterface + class implements:ExtendingExistingOptional, ExistingInterface + object implements:ExtendingExistingOptional, ExistingInterface ImplementsInheritedSkipped - class implements ExtendingNonexistantOptional - object implements ExtendingNonexistantOptional + class implements:ExtendingNonexistantOptional + object implements:ExtendingNonexistantOptional ExtendingExistingOptional - interface extends ExtendingNonexistantOptional + interface extends:ExtendingNonexistantOptional ExtendingNonexistantOptional - interface extends ExtendingNonexistantOptional + interface extends:ExtendingNonexistantOptional From 48ab59a143445055cf97c441efdaecbb82ec6662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 12:34:44 +0200 Subject: [PATCH 09/25] Test Optional interfaces on OPCache --- Zend/tests/optional_interfaces/opcache.phpt | 33 ----------------- ext/opcache/tests/optional_interfaces.phpt | 37 +++++++++++++++++++ .../tests/optional_interfaces_class.inc | 0 3 files changed, 37 insertions(+), 33 deletions(-) delete mode 100644 Zend/tests/optional_interfaces/opcache.phpt create mode 100644 ext/opcache/tests/optional_interfaces.phpt rename Zend/tests/optional_interfaces/opcached_class.inc => ext/opcache/tests/optional_interfaces_class.inc (100%) diff --git a/Zend/tests/optional_interfaces/opcache.phpt b/Zend/tests/optional_interfaces/opcache.phpt deleted file mode 100644 index a2df1ea0e8741..0000000000000 --- a/Zend/tests/optional_interfaces/opcache.phpt +++ /dev/null @@ -1,33 +0,0 @@ ---TEST-- -Optional interfaces are rechecked on subsequent requests ---INI-- -opcache.enable_cli=1 -opcache.enable=1 ---EXTENSIONS-- -opcache ---FILE-- - ---EXPECT-- -OpcachedInterface is not defined -OpcachedClass implements - -OpcachedInterface is defined -OpcachedClass implements OpcachedInterface - -OpcachedInterface is not defined -OpcachedClass implements diff --git a/ext/opcache/tests/optional_interfaces.phpt b/ext/opcache/tests/optional_interfaces.phpt new file mode 100644 index 0000000000000..7244af5803b72 --- /dev/null +++ b/ext/opcache/tests/optional_interfaces.phpt @@ -0,0 +1,37 @@ +--TEST-- +Optional interfaces are rechecked on subsequent requests +--INI-- +opcache.enable_cli=1 +opcache.enable=1 +--EXTENSIONS-- +opcache +--FILE-- + +--EXPECT-- +OpcachedInterface is not defined +OpcachedClass implements: + +OpcachedInterface is defined +OpcachedClass implements:OpcachedInterface + +OpcachedInterface is not defined +OpcachedClass implements: diff --git a/Zend/tests/optional_interfaces/opcached_class.inc b/ext/opcache/tests/optional_interfaces_class.inc similarity index 100% rename from Zend/tests/optional_interfaces/opcached_class.inc rename to ext/opcache/tests/optional_interfaces_class.inc From 8a90459c8d6bcd8ef102d30dcfb43d94d352cdfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C5=BEuris?= Date: Sun, 9 Feb 2025 12:48:26 +0200 Subject: [PATCH 10/25] Specify conflict on an OPCache test --- ext/opcache/tests/optional_interfaces.phpt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/opcache/tests/optional_interfaces.phpt b/ext/opcache/tests/optional_interfaces.phpt index 7244af5803b72..7b73aaa5e14d1 100644 --- a/ext/opcache/tests/optional_interfaces.phpt +++ b/ext/opcache/tests/optional_interfaces.phpt @@ -5,6 +5,8 @@ opcache.enable_cli=1 opcache.enable=1 --EXTENSIONS-- opcache +--CONFLICTS-- +server --FILE-- Date: Sun, 9 Feb 2025 13:18:09 +0200 Subject: [PATCH 11/25] Generate test script --- ext/opcache/tests/optional_interfaces.phpt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ext/opcache/tests/optional_interfaces.phpt b/ext/opcache/tests/optional_interfaces.phpt index 7b73aaa5e14d1..a824547615f04 100644 --- a/ext/opcache/tests/optional_interfaces.phpt +++ b/ext/opcache/tests/optional_interfaces.phpt @@ -12,6 +12,21 @@ server $interfaceFile = __DIR__.'/optional_interfaces_interface.inc'; +file_put_contents(__DIR__.'/optional_interfaces_script.php', <<