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..0bf47525b4baf --- /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/optional_interface_with_parent.phpt b/Zend/tests/optional_interfaces/optional_interface_with_parent.phpt new file mode 100644 index 0000000000000..f572aaab6e7fa --- /dev/null +++ b/Zend/tests/optional_interfaces/optional_interface_with_parent.phpt @@ -0,0 +1,13 @@ +--TEST-- +Optional interface with parent +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/Zend/tests/optional_interfaces/optional_interfaces.phpt b/Zend/tests/optional_interfaces/optional_interfaces.phpt new file mode 100644 index 0000000000000..f3bd78e404bc6 --- /dev/null +++ b/Zend/tests/optional_interfaces/optional_interfaces.phpt @@ -0,0 +1,77 @@ +--TEST-- +Optional interfaces work +--FILE-- + +--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..a0970307555ef --- /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 %soverride.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/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 diff --git a/Zend/tests/optional_interfaces/type_checks.phpt b/Zend/tests/optional_interfaces/type_checks.phpt new file mode 100644 index 0000000000000..369779c5b53b9 --- /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 %s +#1 {main} + thrown in %stype_checks.php on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 0cf1faeb653fe..79a6127d3efc7 100644 --- 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_ast.c b/Zend/zend_ast.c index d4f71ee5ad99d..cb6cc2420847a 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1394,11 +1394,11 @@ static ZEND_COLD void zend_ast_export_ns_name(smart_str *str, zend_ast *ast, int zval *zv = zend_ast_get_zval(ast); if (Z_TYPE_P(zv) == IS_STRING) { - if (ast->attr == ZEND_NAME_FQ) { + if (NAME_QUAL(ast->attr) == ZEND_NAME_FQ) { smart_str_appendc(str, '\\'); - } else if (ast->attr == ZEND_NAME_RELATIVE) { + } else if (NAME_QUAL(ast->attr) == ZEND_NAME_RELATIVE) { smart_str_appends(str, "namespace\\"); - } + } smart_str_append(str, Z_STR_P(zv)); return; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index b4dea55414ffc..d32ece517d433 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1080,12 +1080,12 @@ static zend_string *zend_resolve_non_class_name( return zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0); } - if (type == ZEND_NAME_FQ) { + if (NAME_QUAL(type) == ZEND_NAME_FQ) { *is_fully_qualified = 1; return zend_string_copy(name); } - if (type == ZEND_NAME_RELATIVE) { + if (NAME_QUAL(type) == ZEND_NAME_RELATIVE) { *is_fully_qualified = 1; return zend_prefix_with_ns(name); } @@ -1142,23 +1142,24 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* char *compound; if (ZEND_FETCH_CLASS_DEFAULT != zend_get_class_fetch_type(name)) { - if (type == ZEND_NAME_FQ) { + if (NAME_QUAL(type) == ZEND_NAME_FQ) { zend_error_noreturn(E_COMPILE_ERROR, "'\\%s' is an invalid class name", ZSTR_VAL(name)); } - if (type == ZEND_NAME_RELATIVE) { + if (NAME_QUAL(type) == ZEND_NAME_RELATIVE) { zend_error_noreturn(E_COMPILE_ERROR, "'namespace\\%s' is an invalid class name", ZSTR_VAL(name)); } - ZEND_ASSERT(type == ZEND_NAME_NOT_FQ); + + ZEND_ASSERT(NAME_QUAL(type) == ZEND_NAME_NOT_FQ); return zend_string_copy(name); } - if (type == ZEND_NAME_RELATIVE) { + if (NAME_QUAL(type) == ZEND_NAME_RELATIVE) { return zend_prefix_with_ns(name); } - if (type == ZEND_NAME_FQ) { + if (NAME_QUAL(type) == ZEND_NAME_FQ) { if (ZSTR_VAL(name)[0] == '\\') { /* Remove \ prefix (only relevant if this is a string rather than a label) */ name = zend_string_init(ZSTR_VAL(name) + 1, ZSTR_LEN(name) - 1, 0); @@ -1745,7 +1746,7 @@ uint32_t zend_get_class_fetch_type(const zend_string *name) /* {{{ */ static uint32_t zend_get_class_fetch_type_ast(zend_ast *name_ast) /* {{{ */ { /* Fully qualified names are always default refs */ - if (name_ast->attr == ZEND_NAME_FQ) { + if (NAME_QUAL(name_ast->attr) == ZEND_NAME_FQ) { return ZEND_FETCH_CLASS_DEFAULT; } @@ -2874,7 +2875,7 @@ static void zend_compile_class_ref(znode *result, zend_ast *name_ast, uint32_t f } /* Fully qualified names are always default refs */ - if (name_ast->attr == ZEND_NAME_FQ) { + if (NAME_QUAL(name_ast->attr) == ZEND_NAME_FQ) { result->op_type = IS_CONST; ZVAL_STR(&result->u.constant, zend_resolve_class_name_ast(name_ast)); return; @@ -6960,7 +6961,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) uint8_t type_code = zend_lookup_builtin_type_by_name(type_name); if (type_code != 0) { - if ((ast->attr & ZEND_NAME_NOT_FQ) != ZEND_NAME_NOT_FQ) { + if (NAME_QUAL(ast->attr) != ZEND_NAME_NOT_FQ) { zend_error_noreturn(E_COMPILE_ERROR, "Type declaration '%s' must be unqualified", ZSTR_VAL(zend_string_tolower(type_name))); @@ -7004,7 +7005,7 @@ static zend_type zend_compile_single_typename(zend_ast *ast) zend_string_addref(class_name); } - if (ast->attr == ZEND_NAME_NOT_FQ + if (NAME_QUAL(ast->attr) == ZEND_NAME_NOT_FQ && zend_is_confusable_type(type_name, &correct_name) && zend_is_not_imported(type_name)) { const char *extra = @@ -8061,12 +8062,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 +8979,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 = class_ast->attr & ZEND_NAME_OPTIONAL; } ce->num_interfaces = list->children; @@ -10788,7 +10791,7 @@ static void zend_compile_const(znode *result, zend_ast *ast) /* {{{ */ zend_string *orig_name = zend_ast_get_str(name_ast); zend_string *resolved_name = zend_resolve_const_name(orig_name, name_ast->attr, &is_fully_qualified); - if (zend_string_equals_literal(resolved_name, "__COMPILER_HALT_OFFSET__") || (name_ast->attr != ZEND_NAME_RELATIVE && zend_string_equals_literal(orig_name, "__COMPILER_HALT_OFFSET__"))) { + if (zend_string_equals_literal(resolved_name, "__COMPILER_HALT_OFFSET__") || (NAME_QUAL(name_ast->attr) != ZEND_NAME_RELATIVE && zend_string_equals_literal(orig_name, "__COMPILER_HALT_OFFSET__"))) { zend_ast *last = CG(ast); while (last && last->kind == ZEND_AST_STMT_LIST) { @@ -11230,7 +11233,7 @@ static void zend_compile_const_expr_new(zend_ast **ast_ptr) zval *class_ast_zv = zend_ast_get_zval(class_ast); zval_ptr_dtor_nogc(class_ast_zv); ZVAL_STR(class_ast_zv, class_name); - class_ast->attr = fetch_type << ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT; + class_ast->attr = (fetch_type << ZEND_CONST_EXPR_NEW_FETCH_TYPE_SHIFT) | (fetch_type ? ZEND_NAME_NOT_FQ : ZEND_NAME_FQ); } static void zend_compile_const_expr_closure(zend_ast **ast_ptr) diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 63572ab6623cc..7692cb004e0b0 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1034,6 +1034,10 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_NAME_FQ 0 #define ZEND_NAME_NOT_FQ 1 #define ZEND_NAME_RELATIVE 2 +#define ZEND_NAME_QUALIFIED_MASK 0x03 +#define NAME_QUAL(type) (type & ZEND_NAME_QUALIFIED_MASK) + +#define ZEND_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..7c9fdd78fe055 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -2188,7 +2188,7 @@ ZEND_API void zend_do_implement_interface(zend_class_entry *ce, zend_class_entry } /* }}} */ -static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry **interfaces) /* {{{ */ +static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry **interfaces, size_t num_interface_names) /* {{{ */ { zend_class_entry *iface; uint32_t num_parent_interfaces = ce->parent ? ce->parent->num_interfaces : 0; @@ -2233,11 +2233,12 @@ 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++) { + for (i = 0; i < num_interface_names; 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->interface_names = NULL; } ce->num_interfaces = num_interfaces; @@ -3527,18 +3528,26 @@ 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); } @@ -3589,6 +3598,9 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string Z_CE_P(zv) = ce; } + size_t num_interface_names = ce->num_interfaces; + ce->num_interfaces = num_implementable_interfaces; + if (CG(unlinked_uses)) { zend_hash_index_del(CG(unlinked_uses), (zend_long)(uintptr_t) ce); } @@ -3628,9 +3640,21 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string memcpy(interfaces + num_parent_interfaces, traits_and_interfaces + ce->num_traits, sizeof(zend_class_entry *) * ce->num_interfaces); - zend_do_implement_interfaces(ce, interfaces); - } else if (parent && parent->num_interfaces) { - zend_do_inherit_interfaces(ce, parent); + zend_do_implement_interfaces(ce, interfaces, num_interface_names); + } else { + if (ce->interface_names) { + if (!(ce->ce_flags & ZEND_ACC_CACHED)) { + for (i = 0; i < num_interface_names; 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->interface_names = NULL; + } + 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)) diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8bf..b139053a1699d 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_NAME_OPTIONAL; } +; + class_name_reference: class_name { $$ = $1; } | new_variable { $$ = $1; } diff --git a/ext/opcache/tests/optional_interfaces.phpt b/ext/opcache/tests/optional_interfaces.phpt new file mode 100644 index 0000000000000..ec954cd316ce6 --- /dev/null +++ b/ext/opcache/tests/optional_interfaces.phpt @@ -0,0 +1,106 @@ +--TEST-- +Optional interfaces are rechecked on subsequent requests +--INI-- +opcache.enable_cli=1 +opcache.enable=1 +--EXTENSIONS-- +opcache +--CONFLICTS-- +server +--FILE-- + $hitsBefore) + echo "Class loaded from OPCache\n"; + else + echo "Class loaded from file\n"; + + echo "OpcachedClass implements:".implode(', ', class_implements('OpcachedClass'))."\n\n"; + SCRIPT +); + + +/** + * Launch a server with opcache enabled + */ +include __DIR__.'/php_cli_server.inc'; +$extensionDir = ini_get('extension_dir'); +$extensionDirOption = $extensionDir ? "-d extension_dir=$extensionDir" : ''; +php_cli_server_start("$extensionDirOption -d opcache.enable=1 -d opcache.enable_cli=1 -d zend_extension=opcache"); + +$interfaceFile = __DIR__.'/optional_interfaces_interface.inc'; +$uri = 'http://' . PHP_CLI_SERVER_ADDRESS . '/optional_interfaces_script.php'; + +/** + * First hit + * nothing should be cached yet + * the interface doesn't exist, so the class implements nothing + */ +echo file_get_contents($uri); + + +/** + * Create interface and do the second hit + * OpcachedClass should be loaded from opcache + * The interface exists, so it should be implemented + */ +file_put_contents($interfaceFile, ' +--EXPECT-- +Class is not cached +OpcachedInterface is not defined +Class loaded from file +OpcachedClass implements: + +Class is cached +OpcachedInterface is defined +Class loaded from OPCache +OpcachedClass implements:OpcachedInterface + +Class is cached +OpcachedInterface is not defined +Class loaded from OPCache +OpcachedClass implements: diff --git a/ext/opcache/tests/optional_interfaces_class.inc b/ext/opcache/tests/optional_interfaces_class.inc new file mode 100644 index 0000000000000..a6d906b6e4756 --- /dev/null +++ b/ext/opcache/tests/optional_interfaces_class.inc @@ -0,0 +1,3 @@ +num_interfaces) { uint32_t i; - zend_class_name *interface_names; + zend_interface_name *interface_names; ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_LINKED)); @@ -2028,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 bf0b3d1988144..bf5578c5d9360 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -1055,7 +1055,7 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) zend_accel_store_interned_string(ce->interface_names[i].name); zend_accel_store_interned_string(ce->interface_names[i].lc_name); } - 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); } }