@@ -71,10 +71,19 @@ zend_class_entry *spl_ce_Deque;
7171static const zval empty_entry_list [1 ];
7272
7373typedef struct _spl_deque_entries {
74+ /* The number of elements in the Deque. */
7475 size_t size ;
7576 /* One less than a power of two (the capacity) */
7677 size_t mask ;
78+ /* The offset of the start of the deque in the circular buffer. */
7779 size_t offset ;
80+ /*
81+ * This is a counter which increases when adding elements to the front and decreases when removing elements from the front.
82+ * This is used so that iteration works as expected when the front position is modified during foreach.
83+ *
84+ * This is a uint64_t to work consistently and avoid easily overflowing 4 billion on 32-bit builds.
85+ */
86+ uint64_t iteration_offset ;
7887 /** This is a circular buffer with an offset, size, and capacity(mask + 1) */
7988 zval * circular_buffer ;
8089} spl_deque_entries ;
@@ -116,7 +125,8 @@ typedef struct _spl_deque {
116125/* Used by InternalIterator returned by Deque->getIterator() */
117126typedef struct _spl_deque_it {
118127 zend_object_iterator intern ;
119- zend_long current ;
128+ /* This starts at iteration_offset and increases by one when next() is called. */
129+ uint64_t current ;
120130} spl_deque_it ;
121131
122132static zend_always_inline void spl_deque_push_back (spl_deque * intern , zval * value );
@@ -153,29 +163,43 @@ static bool spl_deque_entries_uninitialized(spl_deque_entries *array)
153163 return array -> circular_buffer == NULL ;
154164}
155165
156- /* Based n zend_hash_check_size. Finds the next largest power of 2. */
166+ /* Based on zend_hash_check_size which supports 32-bit integers . Finds the next largest power of 2. */
157167static inline size_t spl_deque_next_pow2_capacity (size_t nSize ) {
158168 if (nSize < DEQUE_MIN_CAPACITY ) {
159169 return DEQUE_MIN_CAPACITY ;
160170 }
171+ /* Note that for values such as 63 or 31 of the form ((2^n) - 1),
172+ * subtracting and xor are the same things for numbers in the range of 0 to the max. */
161173#ifdef ZEND_WIN32
162174 unsigned long index ;
175+ #if SIZEOF_SIZE_T > 4
176+ if (BitScanReverse64 (& index , nSize - 1 )) {
177+ return 0x2u << ((63 - index ) ^ 0x3f );
178+ }
179+ #else
163180 if (BitScanReverse (& index , nSize - 1 )) {
164181 return 0x2u << ((31 - index ) ^ 0x1f );
165- } else {
166- /* nSize is ensured to be in the valid range, fall back to it
167- rather than using an undefined bis scan result. */
168- return nSize ;
169182 }
183+ #endif
184+ /* nSize is ensured to be in the valid range, fall back to it
185+ * rather than using an undefined bit scan result. */
186+ return nSize ;
170187#elif (defined(__GNUC__ ) || __has_builtin (__builtin_clz )) && defined(PHP_HAVE_BUILTIN_CLZ )
188+ #if SIZEOF_SIZE_T > SIZEOF_INT
189+ return 0x2u << (__builtin_clzl (nSize - 1 ) ^ (sizeof (long ) * 8 - 1 ));
190+ #else
171191 return 0x2u << (__builtin_clz (nSize - 1 ) ^ 0x1f );
192+ #endif
172193#else
173194 nSize -= 1 ;
174195 nSize |= (nSize >> 1 );
175196 nSize |= (nSize >> 2 );
176197 nSize |= (nSize >> 4 );
177198 nSize |= (nSize >> 8 );
178199 nSize |= (nSize >> 16 );
200+ #if SIZEOF_SIZE_T > 4
201+ nSize |= (nSize >> 32 );
202+ #endif
179203 return nSize + 1 ;
180204#endif
181205}
@@ -184,6 +208,7 @@ static void spl_deque_entries_init_from_array(spl_deque_entries *array, zend_arr
184208{
185209 zend_long size = zend_hash_num_elements (values );
186210 array -> offset = 0 ;
211+ array -> iteration_offset = 0 ;
187212 array -> size = 0 ; /* reset size and capacity in case emalloc() fails */
188213 array -> mask = 0 ;
189214 if (size > 0 ) {
@@ -212,6 +237,7 @@ static void spl_deque_entries_init_from_traversable(spl_deque_entries *array, ze
212237 zend_long size = 0 , capacity = 0 ;
213238 array -> size = 0 ;
214239 array -> offset = 0 ;
240+ array -> iteration_offset = 0 ;
215241 array -> circular_buffer = NULL ;
216242 zval * circular_buffer = NULL ;
217243 zval tmp_obj ;
@@ -274,6 +300,7 @@ static void spl_deque_entries_copy_ctor(spl_deque_entries *to, const spl_deque_e
274300 to -> size = 0 ; /* reset size in case emalloc() fails */
275301 to -> mask = 0 ;
276302 to -> offset = 0 ;
303+ to -> iteration_offset = 0 ;
277304 if (!size ) {
278305 to -> circular_buffer = (zval * )empty_entry_list ;
279306 return ;
@@ -321,6 +348,7 @@ static void spl_deque_entries_dtor(spl_deque_entries *array)
321348 array -> offset = 0 ;
322349 array -> size = 0 ;
323350 array -> mask = 0 ;
351+ /* Changing iteration_offset doesn't matter */
324352 do {
325353 if (p == end ) {
326354 p = circular_buffer ;
@@ -504,6 +532,7 @@ PHP_METHOD(Deque, __construct)
504532
505533 if (iterable == NULL ) {
506534 intern -> array .offset = 0 ;
535+ intern -> array .iteration_offset = 0 ;
507536 intern -> array .size = 0 ;
508537 intern -> array .mask = 0 ;
509538 intern -> array .circular_buffer = (zval * )empty_entry_list ;
@@ -535,15 +564,17 @@ static void spl_deque_it_dtor(zend_object_iterator *iter)
535564
536565static void spl_deque_it_rewind (zend_object_iterator * iter )
537566{
538- ((spl_deque_it * )iter )-> current = 0 ;
567+ const spl_deque * object = Z_DEQUE_P (& iter -> data );
568+ ((spl_deque_it * )iter )-> current = object -> array .iteration_offset ;
539569}
540570
541571static int spl_deque_it_valid (zend_object_iterator * iter )
542572{
543573 const spl_deque_it * iterator = (spl_deque_it * )iter ;
544574 const spl_deque * object = Z_DEQUE_P (& iter -> data );
575+ const size_t offset = iterator -> current - object -> array .iteration_offset ;
545576
546- if (iterator -> current >= 0 && ( zend_ulong ) iterator -> current < object -> array .size ) {
577+ if (offset < object -> array .size ) {
547578 return SUCCESS ;
548579 }
549580
@@ -566,8 +597,9 @@ static zval *spl_deque_it_get_current_data(zend_object_iterator *iter)
566597{
567598 const spl_deque_it * iterator = (spl_deque_it * )iter ;
568599 spl_deque * object = Z_DEQUE_P (& iter -> data );
600+ const uint64_t offset = iterator -> current - object -> array .iteration_offset ;
569601
570- zval * data = spl_deque_read_offset_helper (object , iterator -> current );
602+ zval * data = spl_deque_read_offset_helper (object , offset );
571603
572604 if (UNEXPECTED (data == NULL )) {
573605 return & EG (uninitialized_zval );
@@ -580,8 +612,8 @@ static void spl_deque_it_get_current_key(zend_object_iterator *iter, zval *key)
580612{
581613 const spl_deque_it * iterator = (spl_deque_it * )iter ;
582614 const spl_deque * object = Z_DEQUE_P (& iter -> data );
615+ const uint64_t offset = iterator -> current - object -> array .iteration_offset ;
583616
584- size_t offset = iterator -> current ;
585617 if (offset >= object -> array .size ) {
586618 ZVAL_NULL (key );
587619 } else {
@@ -1020,6 +1052,7 @@ PHP_METHOD(Deque, unshift)
10201052 }
10211053
10221054 spl_deque * intern = Z_DEQUE_P (ZEND_THIS );
1055+ intern -> array .iteration_offset -= argc ; /* The front moved backwards by the number of elements */
10231056 size_t old_size = intern -> array .size ;
10241057 const size_t new_size = old_size + argc ;
10251058 size_t mask = intern -> array .mask ;
@@ -1105,6 +1138,7 @@ PHP_METHOD(Deque, shift)
11051138 }
11061139
11071140 intern -> array .size -- ;
1141+ intern -> array .iteration_offset ++ ; /* The front moved forward */
11081142 const size_t old_offset = intern -> array .offset ;
11091143 const size_t old_mask = intern -> array .mask ;
11101144 intern -> array .offset ++ ;
0 commit comments