]> granicus.if.org Git - php/commitdiff
Implement interfaces after all methods available
authorNikita Popov <nikita.ppv@gmail.com>
Wed, 4 Mar 2020 09:10:36 +0000 (10:10 +0100)
committerNikita Popov <nikita.ppv@gmail.com>
Wed, 4 Mar 2020 09:29:21 +0000 (10:29 +0100)
The place where interface implementation handlers is called is
currently ill-defined: If the class implements interfaces itself,
the handlers for both the parent interfaces and the new interfaces
will be called after all methods are registered (post trait use).
If the class does not implement interfaces, then the parent
interface handlers are called early during inheritance (before
methods are inherited).

This commit moves the calls to always occur after all methods are
available. For userland classes this will be post trait import,
at the time where interfaces get implemented (whether the class
itself defines additional interfaces or not). For internal classes
it will be at the end of inheritance, as internal class declarations
do not have proper finalization.

This allows us to simplify the logic for implementing the magic
Iterator / IteratorAggregate interfaces. In particularly we can
now also automatically detect whether an extension of
IteratorAggregate can safely reuse a custom get_iterator handler,
or whether it needs to switch to the userland mechanism. The
Iterator case continues to rely on ZEND_ACC_REUSE_GET_ITERATOR
for this purpose, as a wholesale replacement is not possible there.

Zend/tests/bug48667_1.phpt
Zend/zend_inheritance.c
Zend/zend_interfaces.c

index 2d94aed2bf207086b932c203e3465812fa974b1e..b2e4d28769bb2d2830ce9c7c7627a5781a61d689 100644 (file)
@@ -7,4 +7,4 @@ abstract class A implements Iterator, IteratorAggregate { }
 
 ?>
 --EXPECTF--
-Fatal error: Class A cannot implement both IteratorAggregate and Iterator at the same time in %s on line %d
+Fatal error: Class A cannot implement both Iterator and IteratorAggregate at the same time in %s on line %d
index f15c325d6767947fd598c8815405c952716c42a8..a031207b85e8b33960405a1642356cf0a0cee523 100644 (file)
@@ -141,10 +141,6 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */
        if (EXPECTED(!ce->get_iterator)) {
                ce->get_iterator = parent->get_iterator;
        }
-       if (parent->iterator_funcs_ptr) {
-               /* Must be initialized through iface->interface_gets_implemented() */
-               ZEND_ASSERT(ce->iterator_funcs_ptr);
-       }
        if (EXPECTED(!ce->__get)) {
                ce->__get = parent->__get;
        }
@@ -1185,19 +1181,6 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
        ce->parent = parent_ce;
        ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT;
 
-       /* Inherit interfaces */
-       if (parent_ce->num_interfaces) {
-               if (!ce->num_interfaces) {
-                       zend_do_inherit_interfaces(ce, parent_ce);
-               } else {
-                       uint32_t i;
-
-                       for (i = 0; i < parent_ce->num_interfaces; i++) {
-                               do_implement_interface(ce, parent_ce->interfaces[i]);
-                       }
-               }
-       }
-
        /* Inherit properties */
        if (parent_ce->default_properties_count) {
                zval *src, *dst, *end;
@@ -1378,6 +1361,10 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
        do_inherit_parent_constructor(ce);
 
        if (ce->type == ZEND_INTERNAL_CLASS) {
+               if (parent_ce->num_interfaces) {
+                       zend_do_inherit_interfaces(ce, parent_ce);
+               }
+
                if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
                        ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
                }
@@ -1533,7 +1520,9 @@ static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry
        ce->interfaces = interfaces;
        ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES;
 
-       i = num_parent_interfaces;
+       for (i = 0; i < num_parent_interfaces; i++) {
+               do_implement_interface(ce, ce->interfaces[i]);
+       }
        /* Note that new interfaces can be added during this loop due to interface inheritance.
         * Use num_interfaces rather than ce->num_interfaces to not re-process the new ones. */
        for (; i < num_interfaces; i++) {
@@ -2459,6 +2448,8 @@ ZEND_API int zend_do_link_class(zend_class_entry *ce, zend_string *lc_parent_nam
        }
        if (interfaces) {
                zend_do_implement_interfaces(ce, interfaces);
+       } else if (parent && parent->num_interfaces) {
+               zend_do_inherit_interfaces(ce, parent);
        }
        if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
                zend_verify_abstract_class(ce);
@@ -2542,6 +2533,9 @@ zend_bool zend_try_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce,
                        }
                }
                zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS);
+               if (parent_ce && parent_ce->num_interfaces) {
+                       zend_do_inherit_interfaces(ce, parent_ce);
+               }
                zend_build_properties_info_table(ce);
                if ((ce->ce_flags & (ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) == ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
                        zend_verify_abstract_class(ce);
index fe403b3d7ae466fb233b82b4e8013e338dc8338b..cf8b24d76ab8da9bb9814844cc7aab7edc7dac74 100644 (file)
@@ -313,59 +313,39 @@ static int zend_implement_traversable(zend_class_entry *interface, zend_class_en
 /* {{{ zend_implement_aggregate */
 static int zend_implement_aggregate(zend_class_entry *interface, zend_class_entry *class_type)
 {
-       uint32_t i;
-       int t = -1;
-       zend_class_iterator_funcs *funcs_ptr;
+       if (zend_class_implements_interface(class_type, zend_ce_iterator)) {
+               zend_error_noreturn(E_ERROR,
+                       "Class %s cannot implement both Iterator and IteratorAggregate at the same time",
+                       ZSTR_VAL(class_type->name));
+       }
 
-       if (class_type->get_iterator) {
-               if (class_type->type == ZEND_INTERNAL_CLASS) {
-                       /* inheritance ensures the class has necessary userland methods */
+       zend_function *zf = zend_hash_str_find_ptr(
+               &class_type->function_table, "getiterator", sizeof("getiterator") - 1);
+       if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_new_iterator) {
+               /* get_iterator was explicitly assigned for an internal class. */
+               if (!class_type->parent || class_type->parent->get_iterator != class_type->get_iterator) {
+                       ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS);
                        return SUCCESS;
-               } else if (class_type->get_iterator != zend_user_it_get_new_iterator) {
-                       /* c-level get_iterator cannot be changed (exception being only Traversable is implemented) */
-                       if (class_type->num_interfaces) {
-                               ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_RESOLVED_INTERFACES);
-                               for (i = 0; i < class_type->num_interfaces; i++) {
-                                       if (class_type->interfaces[i] == zend_ce_iterator) {
-                                               zend_error_noreturn(E_ERROR, "Class %s cannot implement both %s and %s at the same time",
-                                                                       ZSTR_VAL(class_type->name),
-                                                                       ZSTR_VAL(interface->name),
-                                                                       ZSTR_VAL(zend_ce_iterator->name));
-                                               return FAILURE;
-                                       }
-                                       if (class_type->interfaces[i] == zend_ce_traversable) {
-                                               t = i;
-                                       }
-                               }
-                       }
-                       if (t == -1) {
-                               return FAILURE;
-                       }
                }
-       }
-       if (class_type->parent
-        && (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) {
-               class_type->get_iterator = class_type->parent->get_iterator;
-               class_type->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR;
-       } else {
-               class_type->get_iterator = zend_user_it_get_new_iterator;
-       }
-       funcs_ptr = class_type->iterator_funcs_ptr;
-       if (class_type->type == ZEND_INTERNAL_CLASS) {
-               if (!funcs_ptr) {
-                       funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs));
-                       class_type->iterator_funcs_ptr = funcs_ptr;
-               }
-               funcs_ptr->zf_new_iterator = zend_hash_str_find_ptr(&class_type->function_table, "getiterator", sizeof("getiterator") - 1);
-       } else {
-               if (!funcs_ptr) {
-                       funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
-                       class_type->iterator_funcs_ptr = funcs_ptr;
-                       memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
-               } else {
-                       funcs_ptr->zf_new_iterator = NULL;
+
+               /* The getIterator() method has not been overwritten, use inherited get_iterator(). */
+               if (zf->common.scope != class_type) {
+                       return SUCCESS;
                }
+
+               /* getIterator() has been overwritten, switch to zend_user_it_get_new_iterator. */
        }
+
+       ZEND_ASSERT(!class_type->iterator_funcs_ptr && "Iterator funcs already set?");
+       zend_class_iterator_funcs *funcs_ptr = class_type->type == ZEND_INTERNAL_CLASS
+               ? pemalloc(sizeof(zend_class_iterator_funcs), 1)
+               : zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
+       class_type->get_iterator = zend_user_it_get_new_iterator;
+       class_type->iterator_funcs_ptr = funcs_ptr;
+
+       memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
+       funcs_ptr->zf_new_iterator = zf;
+
        return SUCCESS;
 }
 /* }}} */
@@ -373,55 +353,35 @@ static int zend_implement_aggregate(zend_class_entry *interface, zend_class_entr
 /* {{{ zend_implement_iterator */
 static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry *class_type)
 {
-       zend_class_iterator_funcs *funcs_ptr;
+       if (zend_class_implements_interface(class_type, zend_ce_aggregate)) {
+               zend_error_noreturn(E_ERROR,
+                       "Class %s cannot implement both Iterator and IteratorAggregate at the same time",
+                       ZSTR_VAL(class_type->name));
+       }
 
        if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_iterator) {
-               if (class_type->type == ZEND_INTERNAL_CLASS) {
-                       /* inheritance ensures the class has the necessary userland methods */
+               if (!class_type->parent || class_type->parent->get_iterator != class_type->get_iterator) {
+                       /* get_iterator was explicitly assigned for an internal class. */
+                       ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS);
                        return SUCCESS;
-               } else {
-                       /* c-level get_iterator cannot be changed */
-                       if (class_type->get_iterator == zend_user_it_get_new_iterator) {
-                               zend_error_noreturn(E_ERROR, "Class %s cannot implement both %s and %s at the same time",
-                                                       ZSTR_VAL(class_type->name),
-                                                       ZSTR_VAL(interface->name),
-                                                       ZSTR_VAL(zend_ce_aggregate->name));
-                       }
-                       return FAILURE;
                }
+               /* Otherwise get_iterator was inherited from the parent by default. */
        }
-       if (class_type->parent
-        && (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) {
-               class_type->get_iterator = class_type->parent->get_iterator;
+
+       if (class_type->parent && (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) {
+               /* Keep the inherited get_iterator handler. */
                class_type->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR;
        } else {
                class_type->get_iterator = zend_user_it_get_iterator;
        }
-       funcs_ptr = class_type->iterator_funcs_ptr;
-       if (class_type->type == ZEND_INTERNAL_CLASS) {
-               if (!funcs_ptr) {
-                       funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs));
-                       class_type->iterator_funcs_ptr = funcs_ptr;
-               } else {
-                       funcs_ptr->zf_rewind = zend_hash_str_find_ptr(&class_type->function_table, "rewind", sizeof("rewind") - 1);
-                       funcs_ptr->zf_valid = zend_hash_str_find_ptr(&class_type->function_table, "valid", sizeof("valid") - 1);
-                       funcs_ptr->zf_key = zend_hash_str_find_ptr(&class_type->function_table, "key", sizeof("key") - 1);
-                       funcs_ptr->zf_current = zend_hash_str_find_ptr(&class_type->function_table, "current", sizeof("current") - 1);
-                       funcs_ptr->zf_next = zend_hash_str_find_ptr(&class_type->function_table, "next", sizeof("next") - 1);
-               }
-       } else {
-               if (!funcs_ptr) {
-                       funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
-                       class_type->iterator_funcs_ptr = funcs_ptr;
-                       memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
-               } else {
-                       funcs_ptr->zf_valid = NULL;
-                       funcs_ptr->zf_current = NULL;
-                       funcs_ptr->zf_key = NULL;
-                       funcs_ptr->zf_next = NULL;
-                       funcs_ptr->zf_rewind = NULL;
-               }
-       }
+
+       ZEND_ASSERT(!class_type->iterator_funcs_ptr && "Iterator funcs already set?");
+       zend_class_iterator_funcs *funcs_ptr = class_type->type == ZEND_INTERNAL_CLASS
+               ? pemalloc(sizeof(zend_class_iterator_funcs), 1)
+               : zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
+       memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
+       class_type->iterator_funcs_ptr = funcs_ptr;
+
        return SUCCESS;
 }
 /* }}} */