@@ -556,17 +556,16 @@ template <class ElemTy> struct ConcurrentReadableArray {
556
556
storage = newStorage;
557
557
Capacity = newCapacity;
558
558
559
- // Use seq_cst here to ensure that the subsequent load of ReaderCount is
560
- // ordered after this store. If ReaderCount is loaded first, then a new
561
- // reader could come in between that load and this store, and then we
562
- // could end up freeing the old storage pointer while it's still in use.
563
- Elements.store (storage, std::memory_order_seq_cst);
559
+ Elements.store (storage, std::memory_order_release);
564
560
}
565
561
566
562
new (&storage->data ()[count]) ElemTy (elem);
567
563
storage->Count .store (count + 1 , std::memory_order_release);
568
564
569
- if (ReaderCount.load (std::memory_order_seq_cst) == 0 )
565
+ // The standard says that std::memory_order_seq_cst only applies to
566
+ // read-modify-write operations, so we need an explicit fence:
567
+ std::atomic_thread_fence (std::memory_order_seq_cst);
568
+ if (ReaderCount.load (std::memory_order_relaxed) == 0 )
570
569
deallocateFreeList ();
571
570
}
572
571
@@ -666,6 +665,13 @@ struct ConcurrentReadableHashMap {
666
665
return x <= 1 ? 0 : log2 (x >> 1 ) + 1 ;
667
666
}
668
667
668
+ // A crude way to detect trivial use-after-free bugs given that a lot of
669
+ // data structure have a strong bias toward bits that are zero.
670
+ #ifndef NDEBUG
671
+ static constexpr uint8_t InlineCapacityDebugBits = 0xC0 ;
672
+ #else
673
+ static constexpr uint8_t InlineCapacityDebugBits = 0 ;
674
+ #endif
669
675
static constexpr uintptr_t InlineIndexBits = 4 ;
670
676
static constexpr uintptr_t InlineIndexMask = 0xF ;
671
677
static constexpr uintptr_t InlineCapacity =
@@ -708,7 +714,7 @@ struct ConcurrentReadableHashMap {
708
714
swift_unreachable (" unknown index size" );
709
715
}
710
716
Value = reinterpret_cast <uintptr_t >(ptr) | static_cast <uintptr_t >(mode);
711
- *reinterpret_cast <uint8_t *>(ptr) = capacityLog2;
717
+ *reinterpret_cast <uint8_t *>(ptr) = capacityLog2 | InlineCapacityDebugBits ;
712
718
}
713
719
714
720
bool valueIsPointer () { return Value & 3 ; }
@@ -739,8 +745,11 @@ struct ConcurrentReadableHashMap {
739
745
}
740
746
741
747
uint8_t getCapacityLog2 () {
742
- if (auto *ptr = pointer ())
743
- return *reinterpret_cast <uint8_t *>(ptr);
748
+ if (auto *ptr = pointer ()) {
749
+ auto result = *reinterpret_cast <uint8_t *>(ptr);
750
+ assert ((result & InlineCapacityDebugBits) == InlineCapacityDebugBits);
751
+ return result & ~InlineCapacityDebugBits;
752
+ }
744
753
return InlineCapacityLog2;
745
754
}
746
755
@@ -859,7 +868,10 @@ struct ConcurrentReadableHashMap {
859
868
// / Free all the arrays in the free lists if there are no active readers. If
860
869
// / there are active readers, do nothing.
861
870
void deallocateFreeListIfSafe () {
862
- if (ReaderCount.load (std::memory_order_seq_cst) == 0 )
871
+ // The standard says that std::memory_order_seq_cst only applies to
872
+ // read-modify-write operations, so we need an explicit fence:
873
+ std::atomic_thread_fence (std::memory_order_seq_cst);
874
+ if (ReaderCount.load (std::memory_order_relaxed) == 0 )
863
875
ConcurrentFreeListNode::freeAll (&FreeList, free);
864
876
}
865
877
0 commit comments