diff options
| author | Nikita Popov <nikita.ppv@gmail.com> | 2020-02-26 16:42:49 +0100 |
|---|---|---|
| committer | Nikita Popov <nikita.ppv@gmail.com> | 2020-06-24 15:31:41 +0200 |
| commit | ff19ec2df3ef0dca8c29be83eddcde58234f4095 (patch) | |
| tree | bc6477f4f4e696cf599ec82a0a6dbf221bc5bc10 /Zend/zend_interfaces.c | |
| parent | 4730b06f1d026047c63980298d358e28e2183de6 (diff) | |
| download | php-git-ff19ec2df3ef0dca8c29be83eddcde58234f4095.tar.gz | |
Introduce InternalIterator
Userland classes that implement Traversable must do so either
through Iterator or IteratorAggregate. The same requirement does
not exist for internal classes: They can implement the internal
get_iterator mechanism, without exposing either the Iterator or
IteratorAggregate APIs. This makes them usable in get_iterator(),
but incompatible with any Iterator based APIs.
A lot of internal classes do this, because exposing the userland
APIs is simply a lot of work. This patch alleviates this issue by
providing a generic InternalIterator class, which acts as an
adapater between get_iterator and Iterator, and can be easily
used by many internal classes. At the same time, we extend the
requirement that Traversable implies Iterator or IteratorAggregate
to internal classes as well.
Closes GH-5216.
Diffstat (limited to 'Zend/zend_interfaces.c')
| -rw-r--r-- | Zend/zend_interfaces.c | 186 |
1 files changed, 178 insertions, 8 deletions
diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index 6ac2721f45..6d35e7159c 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -29,6 +29,9 @@ ZEND_API zend_class_entry *zend_ce_arrayaccess; ZEND_API zend_class_entry *zend_ce_serializable; ZEND_API zend_class_entry *zend_ce_countable; ZEND_API zend_class_entry *zend_ce_stringable; +ZEND_API zend_class_entry *zend_ce_internal_iterator; + +static zend_object_handlers zend_internal_iterator_handlers; /* {{{ zend_call_method Only returns the returned zval if retval_ptr != NULL */ @@ -246,20 +249,16 @@ ZEND_API zend_object_iterator *zend_user_it_get_new_iterator(zend_class_entry *c /* {{{ zend_implement_traversable */ static int zend_implement_traversable(zend_class_entry *interface, zend_class_entry *class_type) { - /* check that class_type is traversable at c-level or implements at least one of 'aggregate' and 'Iterator' */ - uint32_t i; - - if (class_type->get_iterator || (class_type->parent && class_type->parent->get_iterator)) { - return SUCCESS; - } /* Abstract class can implement Traversable only, in which case the extending class must * implement Iterator or IteratorAggregate. */ if (class_type->ce_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) { return SUCCESS; } + + /* Check that class_type implements at least one of 'IteratorAggregate' or 'Iterator' */ if (class_type->num_interfaces) { ZEND_ASSERT(class_type->ce_flags & ZEND_ACC_RESOLVED_INTERFACES); - for (i = 0; i < class_type->num_interfaces; i++) { + for (uint32_t i = 0; i < class_type->num_interfaces; i++) { if (class_type->interfaces[i] == zend_ce_aggregate || class_type->interfaces[i] == zend_ce_iterator) { return SUCCESS; } @@ -441,9 +440,169 @@ static int zend_implement_serializable(zend_class_entry *interface, zend_class_e } /* }}}*/ +typedef struct { + zend_object std; + zend_object_iterator *iter; + zend_bool rewind_called; +} zend_internal_iterator; + +static zend_object *zend_internal_iterator_create(zend_class_entry *ce) { + zend_internal_iterator *intern = emalloc(sizeof(zend_internal_iterator)); + zend_object_std_init(&intern->std, ce); + intern->std.handlers = &zend_internal_iterator_handlers; + intern->iter = NULL; + intern->rewind_called = 0; + return &intern->std; +} + +ZEND_API int zend_create_internal_iterator_zval(zval *return_value, zval *obj) { + zend_class_entry *scope = EG(current_execute_data)->func->common.scope; + ZEND_ASSERT(scope->get_iterator != zend_user_it_get_new_iterator); + zend_object_iterator *iter = scope->get_iterator(Z_OBJCE_P(obj), obj, /* by_ref */ 0); + if (!iter) { + return FAILURE; + } + + zend_internal_iterator *intern = + (zend_internal_iterator *) zend_internal_iterator_create(zend_ce_internal_iterator); + intern->iter = iter; + ZVAL_OBJ(return_value, &intern->std); + return SUCCESS; +} + +static void zend_internal_iterator_free(zend_object *obj) { + zend_internal_iterator *intern = (zend_internal_iterator *) obj; + if (intern->iter) { + zend_iterator_dtor(intern->iter); + } + zend_object_std_dtor(&intern->std); +} + +static zend_internal_iterator *zend_internal_iterator_fetch(zval *This) { + zend_internal_iterator *intern = (zend_internal_iterator *) Z_OBJ_P(This); + if (!intern->iter) { + zend_throw_error(NULL, "The InternalIterator object has not been properly initialized"); + return NULL; + } + return intern; +} + +/* Many iterators will not behave correctly if rewind() is not called, make sure it happens. */ +static int zend_internal_iterator_ensure_rewound(zend_internal_iterator *intern) { + if (!intern->rewind_called) { + zend_object_iterator *iter = intern->iter; + intern->rewind_called = 1; + if (iter->funcs->rewind) { + iter->funcs->rewind(iter); + if (UNEXPECTED(EG(exception))) { + return FAILURE; + } + } + } + return SUCCESS; +} + + +ZEND_METHOD(InternalIterator, __construct) { + zend_throw_error(NULL, "Cannot manually construct InternalIterator"); +} + +ZEND_METHOD(InternalIterator, current) { + ZEND_PARSE_PARAMETERS_NONE(); + + zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS); + if (!intern) { + RETURN_THROWS(); + } + + if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) { + RETURN_THROWS(); + } + + zval *data = intern->iter->funcs->get_current_data(intern->iter); + if (data) { + ZVAL_COPY_DEREF(return_value, data); + } +} + +ZEND_METHOD(InternalIterator, key) { + ZEND_PARSE_PARAMETERS_NONE(); + + zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS); + if (!intern) { + RETURN_THROWS(); + } + + if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) { + RETURN_THROWS(); + } + + if (intern->iter->funcs->get_current_key) { + intern->iter->funcs->get_current_key(intern->iter, return_value); + } else { + RETURN_LONG(intern->iter->index); + } +} + +ZEND_METHOD(InternalIterator, next) { + ZEND_PARSE_PARAMETERS_NONE(); + + zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS); + if (!intern) { + RETURN_THROWS(); + } + + if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) { + RETURN_THROWS(); + } + + intern->iter->funcs->move_forward(intern->iter); + intern->iter->index++; +} + +ZEND_METHOD(InternalIterator, valid) { + ZEND_PARSE_PARAMETERS_NONE(); + + zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS); + if (!intern) { + RETURN_THROWS(); + } + + if (zend_internal_iterator_ensure_rewound(intern) == FAILURE) { + RETURN_THROWS(); + } + + RETURN_BOOL(intern->iter->funcs->valid(intern->iter) == SUCCESS); +} + +ZEND_METHOD(InternalIterator, rewind) { + ZEND_PARSE_PARAMETERS_NONE(); + + zend_internal_iterator *intern = zend_internal_iterator_fetch(ZEND_THIS); + if (!intern) { + RETURN_THROWS(); + } + + if (!intern->iter->funcs->rewind) { + /* Allow calling rewind() if no iteration has happened yet, + * even if the iterator does not support rewinding. */ + if (intern->iter->index != 0) { + zend_throw_error(NULL, "Iterator does not support rewinding"); + RETURN_THROWS(); + } + intern->iter->index = 0; + return; + } + + intern->iter->funcs->rewind(intern->iter); + intern->iter->index = 0; +} + /* {{{ zend_register_interfaces */ ZEND_API void zend_register_interfaces(void) { + zend_class_entry ce; + REGISTER_MAGIC_INTERFACE(traversable, Traversable); REGISTER_MAGIC_INTERFACE(aggregate, IteratorAggregate); @@ -454,7 +613,6 @@ ZEND_API void zend_register_interfaces(void) REGISTER_MAGIC_INTERFACE(serializable, Serializable); - zend_class_entry ce; INIT_CLASS_ENTRY(ce, "ArrayAccess", class_ArrayAccess_methods); zend_ce_arrayaccess = zend_register_internal_interface(&ce); @@ -463,5 +621,17 @@ ZEND_API void zend_register_interfaces(void) INIT_CLASS_ENTRY(ce, "Stringable", class_Stringable_methods); zend_ce_stringable = zend_register_internal_interface(&ce); + + INIT_CLASS_ENTRY(ce, "InternalIterator", class_InternalIterator_methods); + zend_ce_internal_iterator = zend_register_internal_class(&ce); + zend_class_implements(zend_ce_internal_iterator, 1, zend_ce_iterator); + zend_ce_internal_iterator->ce_flags |= ZEND_ACC_FINAL; + zend_ce_internal_iterator->create_object = zend_internal_iterator_create; + zend_ce_internal_iterator->serialize = zend_class_serialize_deny; + zend_ce_internal_iterator->unserialize = zend_class_unserialize_deny; + + memcpy(&zend_internal_iterator_handlers, zend_get_std_object_handlers(), + sizeof(zend_object_handlers)); + zend_internal_iterator_handlers.free_obj = zend_internal_iterator_free; } /* }}} */ |
