Automatically determine whether to reuse get_iterator()

Same as with the IteratorAggregate case, allow reusing get_iterator
if none of the Iterator methods are overridden. Drop the
REUSE_GET_ITERATOR flag that previously allowed ArrayIterator to
opt-in to unconditional get_iterator reuse, and drop the override
handling it did, in favor of the automated approach.
This commit is contained in:
Nikita Popov 2021-09-24 13:11:11 +02:00
parent d0dbf7296b
commit 15bbf6f337
6 changed files with 24 additions and 145 deletions

View File

@ -1,65 +0,0 @@
--TEST--
Iterator exceptions in foreach by reference
--FILE--
<?php
class IT extends ArrayIterator {
private $n = 0;
function __construct($trap = null) {
parent::__construct([0, 1]);
$this->trap = $trap;
}
function trap($trap) {
if ($trap === $this->trap) {
throw new Exception($trap);
}
}
function rewind(): void {$this->trap(__FUNCTION__); parent::rewind();}
function valid(): bool {$this->trap(__FUNCTION__); return parent::valid();}
function key(): string|int|null { $this->trap(__FUNCTION__); return parent::key(); }
function next(): void {$this->trap(__FUNCTION__); parent::next();}
}
foreach(['rewind', 'valid', 'key', 'next'] as $trap) {
$obj = new IT($trap);
try {
// IS_CV
foreach ($obj as $key => &$val) echo "$val\n";
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
unset($obj);
try {
// IS_VAR
foreach (new IT($trap) as $key => &$val) echo "$val\n";
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
try {
// IS_TMP_VAR
foreach ((object)new IT($trap) as $key => &$val) echo "$val\n";
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
}
?>
--EXPECT--
rewind
rewind
rewind
valid
valid
valid
key
key
key
0
next
0
next
0
next

View File

@ -240,7 +240,7 @@ typedef struct _zend_oparray_context {
/* or IS_CONSTANT_VISITED_MARK | | | */
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
/* | | | */
/* Class Flags (unused: 15,21,30,31) | | | */
/* Class Flags (unused: 15,16,21,30,31) | | | */
/* =========== | | | */
/* | | | */
/* Special class types | | | */
@ -269,9 +269,6 @@ typedef struct _zend_oparray_context {
/* User class has methods with static variables | | | */
#define ZEND_HAS_STATIC_IN_METHODS (1 << 14) /* X | | | */
/* | | | */
/* Children must reuse parent get_iterator() | | | */
#define ZEND_ACC_REUSE_GET_ITERATOR (1 << 16) /* X | | | */
/* | | | */
/* Parent class is resolved (CE). | | | */
#define ZEND_ACC_RESOLVED_PARENT (1 << 17) /* X | | | */
/* | | | */

View File

@ -332,20 +332,23 @@ static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry
ZEND_ASSERT(class_type->type == ZEND_INTERNAL_CLASS);
return SUCCESS;
}
/* Otherwise get_iterator was inherited from the parent by default. */
}
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;
/* None of the Iterator methods have been overwritten, use inherited get_iterator(). */
if (zf_rewind->common.scope != class_type && zf_valid->common.scope != class_type &&
zf_key->common.scope != class_type && zf_current->common.scope != class_type &&
zf_next->common.scope != class_type) {
return SUCCESS;
}
/* One of the Iterator methods 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_iterator;
class_type->iterator_funcs_ptr = funcs_ptr;
memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs));

View File

@ -44,11 +44,6 @@ PHPAPI zend_class_entry *spl_ce_RecursiveArrayIterator;
#define SPL_ARRAY_STD_PROP_LIST 0x00000001
#define SPL_ARRAY_ARRAY_AS_PROPS 0x00000002
#define SPL_ARRAY_CHILD_ARRAYS_ONLY 0x00000004
#define SPL_ARRAY_OVERLOADED_REWIND 0x00010000
#define SPL_ARRAY_OVERLOADED_VALID 0x00020000
#define SPL_ARRAY_OVERLOADED_KEY 0x00040000
#define SPL_ARRAY_OVERLOADED_CURRENT 0x00080000
#define SPL_ARRAY_OVERLOADED_NEXT 0x00100000
#define SPL_ARRAY_IS_SELF 0x01000000
#define SPL_ARRAY_USE_OTHER 0x02000000
#define SPL_ARRAY_INT_MASK 0xFFFF0000
@ -229,19 +224,6 @@ static zend_object *spl_array_object_new_ex(zend_class_entry *class_type, zend_o
intern->fptr_count = NULL;
}
}
/* Cache iterator functions if ArrayIterator or derived. Check current's */
/* cache since only current is always required */
if (intern->std.handlers == &spl_handler_ArrayIterator) {
zend_class_iterator_funcs *funcs_ptr = class_type->iterator_funcs_ptr;
if (inherited) {
if (funcs_ptr->zf_rewind->common.scope != parent) intern->ar_flags |= SPL_ARRAY_OVERLOADED_REWIND;
if (funcs_ptr->zf_valid->common.scope != parent) intern->ar_flags |= SPL_ARRAY_OVERLOADED_VALID;
if (funcs_ptr->zf_key->common.scope != parent) intern->ar_flags |= SPL_ARRAY_OVERLOADED_KEY;
if (funcs_ptr->zf_current->common.scope != parent) intern->ar_flags |= SPL_ARRAY_OVERLOADED_CURRENT;
if (funcs_ptr->zf_next->common.scope != parent) intern->ar_flags |= SPL_ARRAY_OVERLOADED_NEXT;
}
}
intern->ht_iter = (uint32_t)-1;
return &intern->std;
@ -952,12 +934,7 @@ static int spl_array_it_valid(zend_object_iterator *iter) /* {{{ */
{
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
HashTable *aht = spl_array_get_hash_table(object);
if (object->ar_flags & SPL_ARRAY_OVERLOADED_VALID) {
return zend_user_it_valid(iter);
} else {
return zend_hash_has_more_elements_ex(aht, spl_array_get_pos_ptr(aht, object));
}
return zend_hash_has_more_elements_ex(aht, spl_array_get_pos_ptr(aht, object));
}
/* }}} */
@ -965,16 +942,11 @@ static zval *spl_array_it_get_current_data(zend_object_iterator *iter) /* {{{ */
{
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
HashTable *aht = spl_array_get_hash_table(object);
if (object->ar_flags & SPL_ARRAY_OVERLOADED_CURRENT) {
return zend_user_it_get_current_data(iter);
} else {
zval *data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
if (data && Z_TYPE_P(data) == IS_INDIRECT) {
data = Z_INDIRECT_P(data);
}
return data;
zval *data = zend_hash_get_current_data_ex(aht, spl_array_get_pos_ptr(aht, object));
if (data && Z_TYPE_P(data) == IS_INDIRECT) {
data = Z_INDIRECT_P(data);
}
return data;
}
/* }}} */
@ -982,12 +954,7 @@ static void spl_array_it_get_current_key(zend_object_iterator *iter, zval *key)
{
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
HashTable *aht = spl_array_get_hash_table(object);
if (object->ar_flags & SPL_ARRAY_OVERLOADED_KEY) {
zend_user_it_get_current_key(iter, key);
} else {
zend_hash_get_current_key_zval_ex(aht, key, spl_array_get_pos_ptr(aht, object));
}
zend_hash_get_current_key_zval_ex(aht, key, spl_array_get_pos_ptr(aht, object));
}
/* }}} */
@ -995,13 +962,8 @@ static void spl_array_it_move_forward(zend_object_iterator *iter) /* {{{ */
{
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
HashTable *aht = spl_array_get_hash_table(object);
if (object->ar_flags & SPL_ARRAY_OVERLOADED_NEXT) {
zend_user_it_move_forward(iter);
} else {
zend_user_it_invalidate_current(iter);
spl_array_next_ex(object, aht);
}
zend_user_it_invalidate_current(iter);
spl_array_next_ex(object, aht);
}
/* }}} */
@ -1021,13 +983,8 @@ static void spl_array_rewind(spl_array_object *intern) /* {{{ */
static void spl_array_it_rewind(zend_object_iterator *iter) /* {{{ */
{
spl_array_object *object = Z_SPLARRAY_P(&iter->data);
if (object->ar_flags & SPL_ARRAY_OVERLOADED_REWIND) {
zend_user_it_rewind(iter);
} else {
zend_user_it_invalidate_current(iter);
spl_array_rewind(object);
}
}
/* }}} */
@ -1099,16 +1056,7 @@ static const zend_object_iterator_funcs spl_array_it_funcs = {
zend_object_iterator *spl_array_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */
{
zend_user_iterator *iterator;
spl_array_object *array_object = Z_SPLARRAY_P(object);
if (by_ref && (array_object->ar_flags & SPL_ARRAY_OVERLOADED_CURRENT)) {
zend_throw_error(NULL, "An iterator cannot be used with foreach by reference");
return NULL;
}
iterator = emalloc(sizeof(zend_user_iterator));
zend_user_iterator *iterator = emalloc(sizeof(zend_user_iterator));
zend_iterator_init(&iterator->it);
ZVAL_OBJ_COPY(&iterator->it.data, Z_OBJ_P(object));
@ -1864,7 +1812,6 @@ PHP_MINIT_FUNCTION(spl_array)
spl_ce_ArrayIterator = register_class_ArrayIterator(spl_ce_SeekableIterator, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable);
spl_ce_ArrayIterator->create_object = spl_array_object_new;
spl_ce_ArrayIterator->get_iterator = spl_array_get_iterator;
spl_ce_ArrayIterator->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR;
memcpy(&spl_handler_ArrayIterator, &spl_handler_ArrayObject, sizeof(zend_object_handlers));
@ -1877,7 +1824,6 @@ PHP_MINIT_FUNCTION(spl_array)
spl_ce_RecursiveArrayIterator = register_class_RecursiveArrayIterator(spl_ce_ArrayIterator, spl_ce_RecursiveIterator);
spl_ce_RecursiveArrayIterator->create_object = spl_array_object_new;
spl_ce_RecursiveArrayIterator->get_iterator = spl_array_get_iterator;
spl_ce_RecursiveArrayIterator->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR;
REGISTER_SPL_CLASS_CONST_LONG(RecursiveArrayIterator, "CHILD_ARRAYS_ONLY", SPL_ARRAY_CHILD_ARRAYS_ONLY);

View File

@ -12,8 +12,7 @@ foreach($it as $k=>$v) { }
?>
--EXPECTF--
Fatal error: Uncaught Error: The object is in an invalid state as the parent constructor was not called in %s:%d
Fatal error: Uncaught Error: Object is not initialized in %s:%d
Stack trace:
#0 %s%ebug54281.php(8): RecursiveIteratorIterator->rewind()
#1 {main}
#0 {main}
thrown in %s%ebug54281.php on line 8

View File

@ -36,7 +36,6 @@ foreach ($recItIt2 as $val) echo "$val\n";
Fatal error: Uncaught Exception in %s
Stack trace:
#0 [internal function]: MyRecursiveIteratorIterator->endchildren()
#1 %s: RecursiveIteratorIterator->next()
#2 {main}
#0 %s(%d): MyRecursiveIteratorIterator->endchildren()
#1 {main}
thrown in %s on line %d