summaryrefslogtreecommitdiff
path: root/Zend/zend_interfaces.c
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2020-02-26 16:42:49 +0100
committerNikita Popov <nikita.ppv@gmail.com>2020-06-24 15:31:41 +0200
commitff19ec2df3ef0dca8c29be83eddcde58234f4095 (patch)
treebc6477f4f4e696cf599ec82a0a6dbf221bc5bc10 /Zend/zend_interfaces.c
parent4730b06f1d026047c63980298d358e28e2183de6 (diff)
downloadphp-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.c186
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;
}
/* }}} */