Skip to content

Commit c05a9c3

Browse files
committed
Implement interfaces after all methods available
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.
1 parent a527c60 commit c05a9c3

File tree

3 files changed

+61
-107
lines changed

3 files changed

+61
-107
lines changed

Zend/tests/bug48667_1.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ abstract class A implements Iterator, IteratorAggregate { }
77

88
?>
99
--EXPECTF--
10-
Fatal error: Class A cannot implement both IteratorAggregate and Iterator at the same time in %s on line %d
10+
Fatal error: Class A cannot implement both Iterator and IteratorAggregate at the same time in %s on line %d

Zend/zend_inheritance.c

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ static void do_inherit_parent_constructor(zend_class_entry *ce) /* {{{ */
141141
if (EXPECTED(!ce->get_iterator)) {
142142
ce->get_iterator = parent->get_iterator;
143143
}
144-
if (parent->iterator_funcs_ptr) {
145-
/* Must be initialized through iface->interface_gets_implemented() */
146-
ZEND_ASSERT(ce->iterator_funcs_ptr);
147-
}
148144
if (EXPECTED(!ce->__get)) {
149145
ce->__get = parent->__get;
150146
}
@@ -1185,19 +1181,6 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
11851181
ce->parent = parent_ce;
11861182
ce->ce_flags |= ZEND_ACC_RESOLVED_PARENT;
11871183

1188-
/* Inherit interfaces */
1189-
if (parent_ce->num_interfaces) {
1190-
if (!ce->num_interfaces) {
1191-
zend_do_inherit_interfaces(ce, parent_ce);
1192-
} else {
1193-
uint32_t i;
1194-
1195-
for (i = 0; i < parent_ce->num_interfaces; i++) {
1196-
do_implement_interface(ce, parent_ce->interfaces[i]);
1197-
}
1198-
}
1199-
}
1200-
12011184
/* Inherit properties */
12021185
if (parent_ce->default_properties_count) {
12031186
zval *src, *dst, *end;
@@ -1378,6 +1361,10 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
13781361
do_inherit_parent_constructor(ce);
13791362

13801363
if (ce->type == ZEND_INTERNAL_CLASS) {
1364+
if (parent_ce->num_interfaces) {
1365+
zend_do_inherit_interfaces(ce, parent_ce);
1366+
}
1367+
13811368
if (ce->ce_flags & ZEND_ACC_IMPLICIT_ABSTRACT_CLASS) {
13821369
ce->ce_flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
13831370
}
@@ -1533,7 +1520,9 @@ static void zend_do_implement_interfaces(zend_class_entry *ce, zend_class_entry
15331520
ce->interfaces = interfaces;
15341521
ce->ce_flags |= ZEND_ACC_RESOLVED_INTERFACES;
15351522

1536-
i = num_parent_interfaces;
1523+
for (i = 0; i < num_parent_interfaces; i++) {
1524+
do_implement_interface(ce, ce->interfaces[i]);
1525+
}
15371526
/* Note that new interfaces can be added during this loop due to interface inheritance.
15381527
* Use num_interfaces rather than ce->num_interfaces to not re-process the new ones. */
15391528
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
24592448
}
24602449
if (interfaces) {
24612450
zend_do_implement_interfaces(ce, interfaces);
2451+
} else if (parent && parent->num_interfaces) {
2452+
zend_do_inherit_interfaces(ce, parent);
24622453
}
24632454
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) {
24642455
zend_verify_abstract_class(ce);
@@ -2542,6 +2533,9 @@ zend_bool zend_try_early_bind(zend_class_entry *ce, zend_class_entry *parent_ce,
25422533
}
25432534
}
25442535
zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS);
2536+
if (parent_ce && parent_ce->num_interfaces) {
2537+
zend_do_inherit_interfaces(ce, parent_ce);
2538+
}
25452539
zend_build_properties_info_table(ce);
25462540
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) {
25472541
zend_verify_abstract_class(ce);

Zend/zend_interfaces.c

Lines changed: 48 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -313,115 +313,75 @@ static int zend_implement_traversable(zend_class_entry *interface, zend_class_en
313313
/* {{{ zend_implement_aggregate */
314314
static int zend_implement_aggregate(zend_class_entry *interface, zend_class_entry *class_type)
315315
{
316-
uint32_t i;
317-
int t = -1;
318-
zend_class_iterator_funcs *funcs_ptr;
316+
if (zend_class_implements_interface(class_type, zend_ce_iterator)) {
317+
zend_error_noreturn(E_ERROR,
318+
"Class %s cannot implement both Iterator and IteratorAggregate at the same time",
319+
ZSTR_VAL(class_type->name));
320+
}
319321

320-
if (class_type->get_iterator) {
321-
if (class_type->type == ZEND_INTERNAL_CLASS) {
322-
/* inheritance ensures the class has necessary userland methods */
322+
zend_function *zf = zend_hash_str_find_ptr(
323+
&class_type->function_table, "getiterator", sizeof("getiterator") - 1);
324+
if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_new_iterator) {
325+
/* get_iterator was explicitly assigned for an internal class. */
326+
if (!class_type->parent || class_type->parent->get_iterator != class_type->get_iterator) {
327+
ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS);
323328
return SUCCESS;
324-
} else if (class_type->get_iterator != zend_user_it_get_new_iterator) {
325-
/* c-level get_iterator cannot be changed (exception being only Traversable is implemented) */
326-
if (class_type->num_interfaces) {
327-
ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_RESOLVED_INTERFACES);
328-
for (i = 0; i < class_type->num_interfaces; i++) {
329-
if (class_type->interfaces[i] == zend_ce_iterator) {
330-
zend_error_noreturn(E_ERROR, "Class %s cannot implement both %s and %s at the same time",
331-
ZSTR_VAL(class_type->name),
332-
ZSTR_VAL(interface->name),
333-
ZSTR_VAL(zend_ce_iterator->name));
334-
return FAILURE;
335-
}
336-
if (class_type->interfaces[i] == zend_ce_traversable) {
337-
t = i;
338-
}
339-
}
340-
}
341-
if (t == -1) {
342-
return FAILURE;
343-
}
344329
}
345-
}
346-
if (class_type->parent
347-
&& (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) {
348-
class_type->get_iterator = class_type->parent->get_iterator;
349-
class_type->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR;
350-
} else {
351-
class_type->get_iterator = zend_user_it_get_new_iterator;
352-
}
353-
funcs_ptr = class_type->iterator_funcs_ptr;
354-
if (class_type->type == ZEND_INTERNAL_CLASS) {
355-
if (!funcs_ptr) {
356-
funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs));
357-
class_type->iterator_funcs_ptr = funcs_ptr;
358-
}
359-
funcs_ptr->zf_new_iterator = zend_hash_str_find_ptr(&class_type->function_table, "getiterator", sizeof("getiterator") - 1);
360-
} else {
361-
if (!funcs_ptr) {
362-
funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
363-
class_type->iterator_funcs_ptr = funcs_ptr;
364-
memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
365-
} else {
366-
funcs_ptr->zf_new_iterator = NULL;
330+
331+
/* The getIterator() method has not been overwritten, use inherited get_iterator(). */
332+
if (zf->common.scope != class_type) {
333+
return SUCCESS;
367334
}
335+
336+
/* getIterator() has been overwritten, switch to zend_user_it_get_new_iterator. */
368337
}
338+
339+
ZEND_ASSERT(!class_type->iterator_funcs_ptr && "Iterator funcs already set?");
340+
zend_class_iterator_funcs *funcs_ptr = class_type->type == ZEND_INTERNAL_CLASS
341+
? pemalloc(sizeof(zend_class_iterator_funcs), 1)
342+
: zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
343+
class_type->get_iterator = zend_user_it_get_new_iterator;
344+
class_type->iterator_funcs_ptr = funcs_ptr;
345+
346+
memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
347+
funcs_ptr->zf_new_iterator = zf;
348+
369349
return SUCCESS;
370350
}
371351
/* }}} */
372352

373353
/* {{{ zend_implement_iterator */
374354
static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry *class_type)
375355
{
376-
zend_class_iterator_funcs *funcs_ptr;
356+
if (zend_class_implements_interface(class_type, zend_ce_aggregate)) {
357+
zend_error_noreturn(E_ERROR,
358+
"Class %s cannot implement both Iterator and IteratorAggregate at the same time",
359+
ZSTR_VAL(class_type->name));
360+
}
377361

378362
if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_iterator) {
379-
if (class_type->type == ZEND_INTERNAL_CLASS) {
380-
/* inheritance ensures the class has the necessary userland methods */
363+
if (!class_type->parent || class_type->parent->get_iterator != class_type->get_iterator) {
364+
/* get_iterator was explicitly assigned for an internal class. */
365+
ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS);
381366
return SUCCESS;
382-
} else {
383-
/* c-level get_iterator cannot be changed */
384-
if (class_type->get_iterator == zend_user_it_get_new_iterator) {
385-
zend_error_noreturn(E_ERROR, "Class %s cannot implement both %s and %s at the same time",
386-
ZSTR_VAL(class_type->name),
387-
ZSTR_VAL(interface->name),
388-
ZSTR_VAL(zend_ce_aggregate->name));
389-
}
390-
return FAILURE;
391367
}
368+
/* Otherwise get_iterator was inherited from the parent by default. */
392369
}
393-
if (class_type->parent
394-
&& (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) {
395-
class_type->get_iterator = class_type->parent->get_iterator;
370+
371+
if (class_type->parent && (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) {
372+
/* Keep the inherited get_iterator handler. */
396373
class_type->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR;
397374
} else {
398375
class_type->get_iterator = zend_user_it_get_iterator;
399376
}
400-
funcs_ptr = class_type->iterator_funcs_ptr;
401-
if (class_type->type == ZEND_INTERNAL_CLASS) {
402-
if (!funcs_ptr) {
403-
funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs));
404-
class_type->iterator_funcs_ptr = funcs_ptr;
405-
} else {
406-
funcs_ptr->zf_rewind = zend_hash_str_find_ptr(&class_type->function_table, "rewind", sizeof("rewind") - 1);
407-
funcs_ptr->zf_valid = zend_hash_str_find_ptr(&class_type->function_table, "valid", sizeof("valid") - 1);
408-
funcs_ptr->zf_key = zend_hash_str_find_ptr(&class_type->function_table, "key", sizeof("key") - 1);
409-
funcs_ptr->zf_current = zend_hash_str_find_ptr(&class_type->function_table, "current", sizeof("current") - 1);
410-
funcs_ptr->zf_next = zend_hash_str_find_ptr(&class_type->function_table, "next", sizeof("next") - 1);
411-
}
412-
} else {
413-
if (!funcs_ptr) {
414-
funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
415-
class_type->iterator_funcs_ptr = funcs_ptr;
416-
memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
417-
} else {
418-
funcs_ptr->zf_valid = NULL;
419-
funcs_ptr->zf_current = NULL;
420-
funcs_ptr->zf_key = NULL;
421-
funcs_ptr->zf_next = NULL;
422-
funcs_ptr->zf_rewind = NULL;
423-
}
424-
}
377+
378+
ZEND_ASSERT(!class_type->iterator_funcs_ptr && "Iterator funcs already set?");
379+
zend_class_iterator_funcs *funcs_ptr = class_type->type == ZEND_INTERNAL_CLASS
380+
? pemalloc(sizeof(zend_class_iterator_funcs), 1)
381+
: zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs));
382+
memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));
383+
class_type->iterator_funcs_ptr = funcs_ptr;
384+
425385
return SUCCESS;
426386
}
427387
/* }}} */

0 commit comments

Comments
 (0)