@@ -514,6 +514,21 @@ ZEND_API HashPosition ZEND_FASTCALL zend_hash_get_current_pos(const HashTable *h
514
514
return _zend_hash_get_current_pos (ht );
515
515
}
516
516
517
+ static void zend_hash_remove_iterator_copies (uint32_t idx ) {
518
+ HashTableIterator * iterators = EG (ht_iterators );
519
+
520
+ HashTableIterator * iter = iterators + idx ;
521
+ uint32_t next_idx = iter -> next_copy ;
522
+ while (next_idx != idx ) {
523
+ uint32_t cur_idx = next_idx ;
524
+ HashTableIterator * cur_iter = iterators + cur_idx ;
525
+ next_idx = cur_iter -> next_copy ;
526
+ cur_iter -> next_copy = cur_idx ; // avoid recursion in zend_hash_iterator_del
527
+ zend_hash_iterator_del (cur_idx );
528
+ }
529
+ iter -> next_copy = idx ;
530
+ }
531
+
517
532
ZEND_API uint32_t ZEND_FASTCALL zend_hash_iterator_add (HashTable * ht , HashPosition pos )
518
533
{
519
534
HashTableIterator * iter = EG (ht_iterators );
@@ -528,6 +543,7 @@ ZEND_API uint32_t ZEND_FASTCALL zend_hash_iterator_add(HashTable *ht, HashPositi
528
543
iter -> ht = ht ;
529
544
iter -> pos = pos ;
530
545
idx = iter - EG (ht_iterators );
546
+ iter -> next_copy = idx ;
531
547
if (idx + 1 > EG (ht_iterators_used )) {
532
548
EG (ht_iterators_used ) = idx + 1 ;
533
549
}
@@ -547,16 +563,49 @@ ZEND_API uint32_t ZEND_FASTCALL zend_hash_iterator_add(HashTable *ht, HashPositi
547
563
iter -> pos = pos ;
548
564
memset (iter + 1 , 0 , sizeof (HashTableIterator ) * 7 );
549
565
idx = iter - EG (ht_iterators );
566
+ iter -> next_copy = idx ;
550
567
EG (ht_iterators_used ) = idx + 1 ;
551
568
return idx ;
552
569
}
553
570
571
+ // To avoid losing track of the HashTable when separating arrays, we track all copies at once.
572
+ static zend_always_inline bool zend_hash_iterator_find_copy_pos (uint32_t idx , HashTable * ht ) {
573
+ HashTableIterator * iter = EG (ht_iterators ) + idx ;
574
+
575
+ uint32_t next_idx = iter -> next_copy ;
576
+ if (EXPECTED (next_idx != idx )) {
577
+ HashTableIterator * copy_iter ;
578
+ while (next_idx != idx ) {
579
+ copy_iter = EG (ht_iterators ) + next_idx ;
580
+ if (copy_iter -> ht == ht ) {
581
+ // We have found the hashtable we are actually iterating over
582
+ // Now clean any intermittent copies and replace the original index by the found one
583
+ if (EXPECTED (iter -> ht ) && EXPECTED (iter -> ht != HT_POISONED_PTR )
584
+ && EXPECTED (!HT_ITERATORS_OVERFLOW (iter -> ht ))) {
585
+ HT_DEC_ITERATORS_COUNT (iter -> ht );
586
+ }
587
+ if (EXPECTED (!HT_ITERATORS_OVERFLOW (ht ))) {
588
+ HT_INC_ITERATORS_COUNT (ht );
589
+ }
590
+ iter -> ht = copy_iter -> ht ;
591
+ iter -> pos = copy_iter -> pos ;
592
+ zend_hash_remove_iterator_copies (idx );
593
+ return true;
594
+ }
595
+ next_idx = copy_iter -> next_copy ;
596
+ }
597
+ zend_hash_remove_iterator_copies (idx );
598
+ }
599
+
600
+ return false;
601
+ }
602
+
554
603
ZEND_API HashPosition ZEND_FASTCALL zend_hash_iterator_pos (uint32_t idx , HashTable * ht )
555
604
{
556
605
HashTableIterator * iter = EG (ht_iterators ) + idx ;
557
606
558
607
ZEND_ASSERT (idx != (uint32_t )-1 );
559
- if (UNEXPECTED (iter -> ht != ht )) {
608
+ if (UNEXPECTED (iter -> ht != ht ) && ! zend_hash_iterator_find_copy_pos ( idx , ht ) ) {
560
609
if (EXPECTED (iter -> ht ) && EXPECTED (iter -> ht != HT_POISONED_PTR )
561
610
&& EXPECTED (!HT_ITERATORS_OVERFLOW (iter -> ht ))) {
562
611
HT_DEC_ITERATORS_COUNT (iter -> ht );
@@ -576,7 +625,7 @@ ZEND_API HashPosition ZEND_FASTCALL zend_hash_iterator_pos_ex(uint32_t idx, zval
576
625
HashTableIterator * iter = EG (ht_iterators ) + idx ;
577
626
578
627
ZEND_ASSERT (idx != (uint32_t )-1 );
579
- if (UNEXPECTED (iter -> ht != ht )) {
628
+ if (UNEXPECTED (iter -> ht != ht ) && ! zend_hash_iterator_find_copy_pos ( idx , ht ) ) {
580
629
if (EXPECTED (iter -> ht ) && EXPECTED (iter -> ht != HT_POISONED_PTR )
581
630
&& EXPECTED (!HT_ITERATORS_OVERFLOW (ht ))) {
582
631
HT_DEC_ITERATORS_COUNT (iter -> ht );
@@ -605,6 +654,10 @@ ZEND_API void ZEND_FASTCALL zend_hash_iterator_del(uint32_t idx)
605
654
}
606
655
iter -> ht = NULL ;
607
656
657
+ if (UNEXPECTED (iter -> next_copy != idx )) {
658
+ zend_hash_remove_iterator_copies (idx );
659
+ }
660
+
608
661
if (idx == EG (ht_iterators_used ) - 1 ) {
609
662
while (idx > 0 && EG (ht_iterators )[idx - 1 ].ht == NULL ) {
610
663
idx -- ;
@@ -2291,6 +2344,22 @@ static zend_always_inline bool zend_array_dup_element(HashTable *source, HashTab
2291
2344
return 1 ;
2292
2345
}
2293
2346
2347
+ // We need to duplicate iterators to be able to search through all copy-on-write copies to find the actually iterated HashTable and position back
2348
+ static void zend_array_dup_ht_iterators (HashTable * source , HashTable * target ) {
2349
+ HashTableIterator * iter = EG (ht_iterators );
2350
+ HashTableIterator * end = iter + EG (ht_iterators_used );
2351
+
2352
+ while (iter != end ) {
2353
+ if (iter -> ht == source ) {
2354
+ uint32_t copy_idx = zend_hash_iterator_add (target , iter -> pos );
2355
+ HashTableIterator * copy_iter = EG (ht_iterators ) + copy_idx ;
2356
+ copy_iter -> next_copy = iter -> next_copy ;
2357
+ iter -> next_copy = copy_idx ;
2358
+ }
2359
+ iter ++ ;
2360
+ }
2361
+ }
2362
+
2294
2363
static zend_always_inline void zend_array_dup_packed_elements (HashTable * source , HashTable * target , bool with_holes )
2295
2364
{
2296
2365
zval * p = source -> arPacked ;
@@ -2305,6 +2374,10 @@ static zend_always_inline void zend_array_dup_packed_elements(HashTable *source,
2305
2374
}
2306
2375
p ++ ; q ++ ;
2307
2376
} while (p != end );
2377
+
2378
+ if (UNEXPECTED (HT_HAS_ITERATORS (source ))) {
2379
+ zend_array_dup_ht_iterators (source , target );
2380
+ }
2308
2381
}
2309
2382
2310
2383
static zend_always_inline uint32_t zend_array_dup_elements (HashTable * source , HashTable * target , bool static_keys , bool with_holes )
@@ -2314,19 +2387,44 @@ static zend_always_inline uint32_t zend_array_dup_elements(HashTable *source, Ha
2314
2387
Bucket * q = target -> arData ;
2315
2388
Bucket * end = p + source -> nNumUsed ;
2316
2389
2390
+ if (UNEXPECTED (HT_HAS_ITERATORS (source ))) {
2391
+ zend_array_dup_ht_iterators (source , target );
2392
+ }
2393
+
2317
2394
do {
2318
2395
if (!zend_array_dup_element (source , target , idx , p , q , 0 , static_keys , with_holes )) {
2319
2396
uint32_t target_idx = idx ;
2320
2397
2321
2398
idx ++ ; p ++ ;
2322
- while (p != end ) {
2323
- if (zend_array_dup_element (source , target , target_idx , p , q , 0 , static_keys , with_holes )) {
2324
- if (source -> nInternalPointer == idx ) {
2325
- target -> nInternalPointer = target_idx ;
2399
+ if (EXPECTED (!HT_HAS_ITERATORS (target ))) {
2400
+ while (p != end ) {
2401
+ if (zend_array_dup_element (source , target , target_idx , p , q , 0 , static_keys , with_holes )) {
2402
+ if (source -> nInternalPointer == idx ) {
2403
+ target -> nInternalPointer = target_idx ;
2404
+ }
2405
+ target_idx ++ ; q ++ ;
2406
+ }
2407
+ idx ++ ; p ++ ;
2408
+ }
2409
+ } else {
2410
+ target -> nNumUsed = source -> nNumOfElements ;
2411
+ uint32_t iter_pos = zend_hash_iterators_lower_pos (target , idx );
2412
+
2413
+ while (p != end ) {
2414
+ if (zend_array_dup_element (source , target , target_idx , p , q , 0 , static_keys , with_holes )) {
2415
+ if (source -> nInternalPointer == idx ) {
2416
+ target -> nInternalPointer = target_idx ;
2417
+ }
2418
+ if (UNEXPECTED (idx >= iter_pos )) {
2419
+ do {
2420
+ zend_hash_iterators_update (target , iter_pos , target_idx );
2421
+ iter_pos = zend_hash_iterators_lower_pos (target , iter_pos + 1 );
2422
+ } while (iter_pos < idx );
2423
+ }
2424
+ target_idx ++ ; q ++ ;
2326
2425
}
2327
- target_idx ++ ; q ++ ;
2426
+ idx ++ ; p ++ ;
2328
2427
}
2329
- idx ++ ; p ++ ;
2330
2428
}
2331
2429
return target_idx ;
2332
2430
}
0 commit comments