Skip to content

Commit

Permalink
Introduce InternalIterator
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nikic committed Jun 24, 2020
1 parent 4730b06 commit ff19ec2
Show file tree
Hide file tree
Showing 33 changed files with 488 additions and 30 deletions.
15 changes: 15 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,17 @@ PHP 8.0 UPGRADE NOTES
longer referenced.
. The deprecated parameter `$version` of curl_version() has been removed.

- Date:
. DatePeriod now implements IteratorAggregate (instead of Traversable).

- DOM:
. DOMNamedNodeMap now implements IteratorAggregate (instead of Traversable).
. DOMNodeList now implements IteratorAggregate (instead of Traversable).

- Intl:
. IntlBreakIterator now implements IteratorAggregate (instead of Traversable).
. ResourceBundle now implements IteratorAggregate (instead of Traversable).

- Enchant:
. The enchant extension now uses libenchant-2 by default when available.
libenchant version 1 is still supported but is deprecated and could
Expand All @@ -786,9 +797,13 @@ PHP 8.0 UPGRADE NOTES
- MBString:
. The Unicode data tables have been updated to version 13.0.0.

- PDO:
. PDOStatement now implements IteratorAggregate (instead of Traversable).

- MySQLi / PDO MySQL:
. When mysqlnd is not used (which is the default and recommended option),
the minimum supported libmysqlclient version is now 5.1.
. mysqli_result now implements IteratorAggregate (instead of Traversable).

- PGSQL / PDO PGSQL:
. The PGSQL and PDO PGSQL extensions now require at least libpq 9.1.
Expand Down
11 changes: 11 additions & 0 deletions UPGRADING.INTERNALS
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ PHP 8.0 INTERNALS UPGRADE NOTES
o. cast_object() object handler is now required
p. ARG_COUNT() macro removed
q. GC_COLLECTABLE flag
r. Cannot implement Traversable only

2. Build system changes
a. Abstract
Expand Down Expand Up @@ -122,6 +123,16 @@ PHP 8.0 INTERNALS UPGRADE NOTES
Assignments to GC_TYPE_INFO() might need to be changed to properly
set the value of the GC_NOT_COLLECTABLE flag.

r. Just for for userland classes, it is no longer allowed to implement only
the Traversable interface. Instead, it is necessary to implement either
Iterator or IteratorAggregate. You can do the latter by implementing
zend_ce_aggregate and providing the following method implementation:

ZEND_METHOD(MyClass, getIterator) {
ZEND_PARSE_PARAMETERS_NONE();
zend_create_internal_iterator_zval(return_value, ZEND_THIS);
}

========================
2. Build system changes
========================
Expand Down
186 changes: 178 additions & 8 deletions Zend/zend_interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand All @@ -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;
}
/* }}} */
2 changes: 2 additions & 0 deletions Zend/zend_interfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ ZEND_API int zend_user_unserialize(zval *object, zend_class_entry *ce, const uns
ZEND_API int zend_class_serialize_deny(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
ZEND_API int zend_class_unserialize_deny(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

ZEND_API int zend_create_internal_iterator_zval(zval *return_value, zval *obj);

END_EXTERN_C()

#endif /* ZEND_INTERFACES_H */
17 changes: 17 additions & 0 deletions Zend/zend_interfaces.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,20 @@ interface Stringable
{
public function __toString(): string;
}

final class InternalIterator implements Iterator
{
private function __construct();

/** @return mixed */
public function current();

/** @return mixed */
public function key();

public function next(): void;

public function valid(): bool;

public function rewind(): void;
}
31 changes: 31 additions & 0 deletions Zend/zend_interfaces_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,27 @@ ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Stringable___toString, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()

#define arginfo_class_InternalIterator___construct arginfo_class_IteratorAggregate_getIterator

#define arginfo_class_InternalIterator_current arginfo_class_IteratorAggregate_getIterator

#define arginfo_class_InternalIterator_key arginfo_class_IteratorAggregate_getIterator

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_InternalIterator_next, 0, 0, IS_VOID, 0)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_InternalIterator_valid, 0, 0, _IS_BOOL, 0)
ZEND_END_ARG_INFO()

#define arginfo_class_InternalIterator_rewind arginfo_class_InternalIterator_next


ZEND_METHOD(InternalIterator, __construct);
ZEND_METHOD(InternalIterator, current);
ZEND_METHOD(InternalIterator, key);
ZEND_METHOD(InternalIterator, next);
ZEND_METHOD(InternalIterator, valid);
ZEND_METHOD(InternalIterator, rewind);


static const zend_function_entry class_Traversable_methods[] = {
Expand Down Expand Up @@ -88,3 +108,14 @@ static const zend_function_entry class_Stringable_methods[] = {
ZEND_ABSTRACT_ME_WITH_FLAGS(Stringable, __toString, arginfo_class_Stringable___toString, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT)
ZEND_FE_END
};


static const zend_function_entry class_InternalIterator_methods[] = {
ZEND_ME(InternalIterator, __construct, arginfo_class_InternalIterator___construct, ZEND_ACC_PRIVATE)
ZEND_ME(InternalIterator, current, arginfo_class_InternalIterator_current, ZEND_ACC_PUBLIC)
ZEND_ME(InternalIterator, key, arginfo_class_InternalIterator_key, ZEND_ACC_PUBLIC)
ZEND_ME(InternalIterator, next, arginfo_class_InternalIterator_next, ZEND_ACC_PUBLIC)
ZEND_ME(InternalIterator, valid, arginfo_class_InternalIterator_valid, ZEND_ACC_PUBLIC)
ZEND_ME(InternalIterator, rewind, arginfo_class_InternalIterator_rewind, ZEND_ACC_PUBLIC)
ZEND_FE_END
};
15 changes: 11 additions & 4 deletions Zend/zend_weakrefs.c
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,15 @@ ZEND_METHOD(WeakMap, count)
RETURN_LONG(count);
}

ZEND_METHOD(WeakMap, getIterator)
{
if (zend_parse_parameters_none() == FAILURE) {
return;
}

zend_create_internal_iterator_zval(return_value, ZEND_THIS);
}

void zend_register_weakref_ce(void) /* {{{ */
{
zend_class_entry ce;
Expand All @@ -597,16 +606,14 @@ void zend_register_weakref_ce(void) /* {{{ */
INIT_CLASS_ENTRY(ce, "WeakMap", class_WeakMap_methods);
zend_ce_weakmap = zend_register_internal_class(&ce);
zend_ce_weakmap->ce_flags |= ZEND_ACC_FINAL | ZEND_ACC_NO_DYNAMIC_PROPERTIES;
zend_class_implements(
zend_ce_weakmap, 3, zend_ce_arrayaccess, zend_ce_countable, zend_ce_aggregate);

zend_ce_weakmap->create_object = zend_weakmap_create_object;
zend_ce_weakmap->get_iterator = zend_weakmap_get_iterator;
zend_ce_weakmap->serialize = zend_class_serialize_deny;
zend_ce_weakmap->unserialize = zend_class_unserialize_deny;

/* Must happen after get_iterator is assigned. */
zend_class_implements(
zend_ce_weakmap, 3, zend_ce_arrayaccess, zend_ce_countable, zend_ce_traversable);

memcpy(&zend_weakmap_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
zend_weakmap_handlers.offset = XtOffsetOf(zend_weakmap, std);
zend_weakmap_handlers.free_obj = zend_weakmap_free_obj;
Expand Down

0 comments on commit ff19ec2

Please sign in to comment.