diff --git a/NEWS b/NEWS index 6532113ff13..40408d8b260 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ PHP NEWS (Girgias) . Fixed bug GH-12073 (Segfault when freeing incompletely initialized closures). (ilutov) + . Fixed bug GH-12060 (Internal iterator rewind handler is called twice). + (ju1ius) - DOM: . Fix memory leak when setting an invalid DOMDocument encoding. (nielsdos) diff --git a/Zend/zend_interfaces.c b/Zend/zend_interfaces.c index b4e43b78efa..42fbccd4a74 100644 --- a/Zend/zend_interfaces.c +++ b/Zend/zend_interfaces.c @@ -623,6 +623,7 @@ ZEND_METHOD(InternalIterator, rewind) { RETURN_THROWS(); } + intern->rewind_called = 1; if (!intern->iter->funcs->rewind) { /* Allow calling rewind() if no iteration has happened yet, * even if the iterator does not support rewinding. */ diff --git a/ext/zend_test/config.m4 b/ext/zend_test/config.m4 index c33ad74f0c8..4fda9d2cab5 100644 --- a/ext/zend_test/config.m4 +++ b/ext/zend_test/config.m4 @@ -4,5 +4,5 @@ PHP_ARG_ENABLE([zend-test], [Enable zend_test extension])]) if test "$PHP_ZEND_TEST" != "no"; then - PHP_NEW_EXTENSION(zend_test, test.c observer.c fiber.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) + PHP_NEW_EXTENSION(zend_test, test.c observer.c fiber.c iterators.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) fi diff --git a/ext/zend_test/config.w32 b/ext/zend_test/config.w32 index a44a7fc3d9d..8d68facffb3 100644 --- a/ext/zend_test/config.w32 +++ b/ext/zend_test/config.w32 @@ -3,6 +3,6 @@ ARG_ENABLE("zend-test", "enable zend_test extension", "no"); if (PHP_ZEND_TEST != "no") { - EXTENSION("zend_test", "test.c observer.c fiber.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); + EXTENSION("zend_test", "test.c observer.c fiber.c iterators.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1"); ADD_FLAG("CFLAGS_ZEND_TEST", "/D PHP_ZEND_TEST_EXPORTS "); } diff --git a/ext/zend_test/iterators.c b/ext/zend_test/iterators.c new file mode 100644 index 00000000000..53756ca79e1 --- /dev/null +++ b/ext/zend_test/iterators.c @@ -0,0 +1,121 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#include "iterators.h" +#include "zend_API.h" +#include "iterators_arginfo.h" + +#include +#include "php.h" + +#define DUMP(s) php_output_write((s), sizeof((s)) - 1) + +static zend_class_entry *traversable_test_ce; + +// Dummy iterator that yields numbers from 0..4, +// while printing operations to the output buffer +typedef struct { + zend_object_iterator intern; + zval current; +} test_traversable_it; + +static test_traversable_it *test_traversable_it_fetch(zend_object_iterator *iter) { + return (test_traversable_it *)iter; +} + +static void test_traversable_it_dtor(zend_object_iterator *iter) { + DUMP("TraversableTest::drop\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + zval_ptr_dtor(&iterator->intern.data); +} + +static void test_traversable_it_rewind(zend_object_iterator *iter) { + DUMP("TraversableTest::rewind\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + ZVAL_LONG(&iterator->current, 0); +} + +static void test_traversable_it_next(zend_object_iterator *iter) { + DUMP("TraversableTest::next\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + ZVAL_LONG(&iterator->current, Z_LVAL(iterator->current) + 1); +} + +static int test_traversable_it_valid(zend_object_iterator *iter) { + DUMP("TraversableTest::valid\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + if (Z_LVAL(iterator->current) < 4) { + return SUCCESS; + } + return FAILURE; +} + +static void test_traversable_it_key(zend_object_iterator *iter, zval *return_value) { + DUMP("TraversableTest::key\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + ZVAL_LONG(return_value, Z_LVAL(iterator->current)); +} + +static zval *test_traversable_it_current(zend_object_iterator *iter) { + DUMP("TraversableTest::current\n"); + test_traversable_it *iterator = test_traversable_it_fetch(iter); + return &iterator->current; +} + +static const zend_object_iterator_funcs test_traversable_it_vtable = { + test_traversable_it_dtor, + test_traversable_it_valid, + test_traversable_it_current, + test_traversable_it_key, + test_traversable_it_next, + test_traversable_it_rewind, + NULL, // invalidate_current + NULL, // get_gc +}; + +static zend_object_iterator *test_traversable_get_iterator( + zend_class_entry *ce, + zval *object, + int by_ref +) { + test_traversable_it *iterator; + + if (by_ref) { + zend_throw_error(NULL, "An iterator cannot be used with foreach by reference"); + return NULL; + } + + iterator = emalloc(sizeof(test_traversable_it)); + zend_iterator_init((zend_object_iterator*)iterator); + + ZVAL_OBJ_COPY(&iterator->intern.data, Z_OBJ_P(object)); + iterator->intern.funcs = &test_traversable_it_vtable; + ZVAL_LONG(&iterator->current, 0); + + return (zend_object_iterator*)iterator; +} + +ZEND_METHOD(ZendTest_Iterators_TraversableTest, __construct) { + ZEND_PARSE_PARAMETERS_NONE(); +} + +ZEND_METHOD(ZendTest_Iterators_TraversableTest, getIterator) { + ZEND_PARSE_PARAMETERS_NONE(); + zend_create_internal_iterator_zval(return_value, ZEND_THIS); +} + +void zend_test_iterators_init(void) { + traversable_test_ce = register_class_ZendTest_Iterators_TraversableTest(zend_ce_aggregate); + traversable_test_ce->get_iterator = test_traversable_get_iterator; +} diff --git a/ext/zend_test/iterators.h b/ext/zend_test/iterators.h new file mode 100644 index 00000000000..cef09109e23 --- /dev/null +++ b/ext/zend_test/iterators.h @@ -0,0 +1,21 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | https://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ +*/ + +#ifndef ZEND_TEST_ITERATORS_H +#define ZEND_TEST_ITERATORS_H + +void zend_test_iterators_init(void); + +#endif + diff --git a/ext/zend_test/iterators.stub.php b/ext/zend_test/iterators.stub.php new file mode 100644 index 00000000000..9b681e36b94 --- /dev/null +++ b/ext/zend_test/iterators.stub.php @@ -0,0 +1,14 @@ +ce_flags |= ZEND_ACC_FINAL; + zend_class_implements(class_entry, 1, class_entry_IteratorAggregate); + + return class_entry; +} diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 8b2ca68e4e7..88b7d5d904e 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -24,6 +24,7 @@ #include "php_test.h" #include "observer.h" #include "fiber.h" +#include "iterators.h" #include "zend_attributes.h" #include "zend_enum.h" #include "zend_interfaces.h" @@ -845,6 +846,7 @@ PHP_MINIT_FUNCTION(zend_test) zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU); zend_test_fiber_init(); + zend_test_iterators_init(); return SUCCESS; } diff --git a/ext/zend_test/tests/iterators/double-rewind.phpt b/ext/zend_test/tests/iterators/double-rewind.phpt new file mode 100644 index 00000000000..179dd1de107 --- /dev/null +++ b/ext/zend_test/tests/iterators/double-rewind.phpt @@ -0,0 +1,40 @@ +--TEST-- +Tests that internal iterator's rewind function is called once +--EXTENSIONS-- +zend_test +--FILE-- +getIterator(); +var_dump($it); +foreach ($it as $key => $value) { + echo "{$key} => {$value}\n"; +} +?> +--EXPECT-- +object(InternalIterator)#3 (0) { +} +TraversableTest::rewind +TraversableTest::valid +TraversableTest::current +TraversableTest::key +0 => 0 +TraversableTest::next +TraversableTest::valid +TraversableTest::current +TraversableTest::key +1 => 1 +TraversableTest::next +TraversableTest::valid +TraversableTest::current +TraversableTest::key +2 => 2 +TraversableTest::next +TraversableTest::valid +TraversableTest::current +TraversableTest::key +3 => 3 +TraversableTest::next +TraversableTest::valid +TraversableTest::drop