[libcxx-commits] [libcxx] fe04edc - [libc++] Fix strict aliasing violation for `deque::const_iterator` (#136067)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Mar 10 01:00:43 PDT 2026


Author: A. Jiang
Date: 2026-03-10T16:00:38+08:00
New Revision: fe04edc5a06c2a01cdcd5c56ba5622e2b96b7294

URL: https://github.com/llvm/llvm-project/commit/fe04edc5a06c2a01cdcd5c56ba5622e2b96b7294
DIFF: https://github.com/llvm/llvm-project/commit/fe04edc5a06c2a01cdcd5c56ba5622e2b96b7294.diff

LOG: [libc++] Fix strict aliasing violation for `deque::const_iterator` (#136067)

When the allocators use fancy pointers, the internal map of `deque`
stores `FancyPtr<T>` objects, and the previous strategy accessed these
objects via `const FancyPtr<const T>` lvalues, which usually caused core
language undefined behavior. Now `const_iterator` stores `FancyPtr<const
FancyPtr<T>>` instead of `FancyPtr<const FancyPtr<const T>>`, and ABI
break can happen when such two types have incompatible layouts.

This is necessary for reducing undefined behavior and `constexpr`
support for `deque` in C++26, and I currently don't want to provide any
way to opt-out of that behavior.

For `iterator`, the current strategy makes it store
`FancyPtr<FancyPtr<T>>`. But it would make more sense to also store
`FancyPtr<const FancyPtr<T>>` because we never modify the map via
`iterator`.

For some pathological combinations of allocators and fancy pointers, the
rebinding trick doesn't work. These cases are rejected by
`static_assert`.

The existing test coverage seems to be sufficient.

Added: 
    

Modified: 
    libcxx/docs/ReleaseNotes/23.rst
    libcxx/include/deque

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst
index bcf527869ac94..4441a8aed198c 100644
--- a/libcxx/docs/ReleaseNotes/23.rst
+++ b/libcxx/docs/ReleaseNotes/23.rst
@@ -79,5 +79,14 @@ ABI Affecting Changes
   ``__bounded_iter<Iter>`` directly if ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR`` and
   ``_LIBCPP_ABI_BOUNDED_ITERATORS_IN_STRING`` are defined.
 
+- The ``const_iterator`` member type of ``std::deque`` is now corrected to hold a (possibly fancy) pointer to the
+  (possibly fancy) pointer allocated in the internal map. E.g. when the allocators use fancy pointers, the internal map
+  stores ``FancyPtr<T>`` objects, and the previous strategy accessed these objects via ``const FancyPtr<const T>``
+  lvalues, causing undefined behavior. Now ``const_iterator`` stores ``FancyPtr<const FancyPtr<T>>`` instead of
+  ``FancyPtr<const FancyPtr<const T>>``, which is technically an ABI break when these two types have incompatible
+  layouts.  The ``iterator`` member type is also changed to store ``FancyPtr<const FancyPtr<T>>`` because the internal
+  map is never modified via an ``iterator``. This is necessary for reducing undefined behavior and for adding
+  ``constexpr`` support for ``deque`` in C++26, so we do not provide any way to opt-out of that behavior.
+
 Build System Changes
 --------------------

diff  --git a/libcxx/include/deque b/libcxx/include/deque
index 94a8296a939ba..c8c6889f1a165 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -283,7 +283,18 @@ template <class _ValueType,
 #  endif
           >
 class __deque_iterator {
-  typedef _MapPointer __map_iterator;
+  using __map_iterator _LIBCPP_NODEBUG = __rebind_pointer_t<_Pointer, const __rebind_pointer_t<_Pointer, _ValueType> >;
+
+// TODO(LLVM 25): Remove this check
+#  ifndef _LIBCPP_ABI_DEQUE_ITERATORS
+  static_assert(
+      sizeof(__map_iterator) == sizeof(_MapPointer) && _LIBCPP_ALIGNOF(__map_iterator) == _LIBCPP_ALIGNOF(_MapPointer),
+      "It looks like you are using fancy pointers such that FancyPtr<const FancyPtr<const value_type>> is not "
+      "layout-compatible with FancyPtr<FancyPtr<value_type>> or FancyPtr<const FancyPtr<value_type>>. "
+      "This means that your ABI is being broken between LLVM 22 and LLVM 23 if deque::iterator or "
+      "deque::const_iterator is used. If you don't care about your ABI being broken, define the "
+      "_LIBCPP_ABI_DEQUE_ITERATORS macro to silence this diagnostic.");
+#  endif
 
 public:
   typedef _Pointer pointer;
@@ -459,7 +470,7 @@ private:
       __deque_iterator<_ValueType, _Pointer, _Reference, _MapPointer, _DiffType, _BlockSize>;
 
 public:
-  using __segment_iterator _LIBCPP_NODEBUG = _MapPointer;
+  using __segment_iterator _LIBCPP_NODEBUG = typename _Iterator::__map_iterator;
   using __local_iterator _LIBCPP_NODEBUG   = _Pointer;
 
   static _LIBCPP_HIDE_FROM_ABI __segment_iterator __segment(_Iterator __iter) { return __iter.__m_iter_; }
@@ -725,24 +736,24 @@ public:
   // iterators:
 
   [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI iterator begin() _NOEXCEPT {
-    __map_pointer __mp = __map_.begin() + __start_ / __block_size;
+    auto __mp = __map_.begin() + __start_ / __block_size;
     return iterator(__mp, __map_.empty() ? 0 : *__mp + __start_ % __block_size);
   }
 
   [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI const_iterator begin() const _NOEXCEPT {
-    __map_const_pointer __mp = static_cast<__map_const_pointer>(__map_.begin() + __start_ / __block_size);
+    auto __mp = __map_.begin() + __start_ / __block_size;
     return const_iterator(__mp, __map_.empty() ? 0 : *__mp + __start_ % __block_size);
   }
 
   [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI iterator end() _NOEXCEPT {
-    size_type __p      = size() + __start_;
-    __map_pointer __mp = __map_.begin() + __p / __block_size;
+    size_type __p = size() + __start_;
+    auto __mp     = __map_.begin() + __p / __block_size;
     return iterator(__mp, __map_.empty() ? 0 : *__mp + __p % __block_size);
   }
 
   [[__nodiscard__]] _LIBCPP_HIDE_FROM_ABI const_iterator end() const _NOEXCEPT {
-    size_type __p            = size() + __start_;
-    __map_const_pointer __mp = static_cast<__map_const_pointer>(__map_.begin() + __p / __block_size);
+    size_type __p = size() + __start_;
+    auto __mp     = __map_.begin() + __p / __block_size;
     return const_iterator(__mp, __map_.empty() ? 0 : *__mp + __p % __block_size);
   }
 
@@ -2220,7 +2231,7 @@ deque<_Tp, _Allocator>::__move_and_check(iterator __f, iterator __l, iterator __
       __fe = __fb + __bs;
     }
     if (__fb <= __vt && __vt < __fe)
-      __vt = (const_iterator(static_cast<__map_const_pointer>(__f.__m_iter_), __vt) -= __f - __r).__ptr_;
+      __vt = (const_iterator(__f.__m_iter_, __vt) -= __f - __r).__ptr_;
     __r = std::move(__fb, __fe, __r);
     __n -= __bs;
     __f += __bs;
@@ -2247,7 +2258,7 @@ deque<_Tp, _Allocator>::__move_backward_and_check(iterator __f, iterator __l, it
       __lb = __le - __bs;
     }
     if (__lb <= __vt && __vt < __le)
-      __vt = (const_iterator(static_cast<__map_const_pointer>(__l.__m_iter_), __vt) += __r - __l - 1).__ptr_;
+      __vt = (const_iterator(__l.__m_iter_, __vt) += __r - __l - 1).__ptr_;
     __r = std::move_backward(__lb, __le, __r);
     __n -= __bs;
     __l -= __bs - 1;
@@ -2273,7 +2284,7 @@ void deque<_Tp, _Allocator>::__move_construct_and_check(iterator __f, iterator _
       __fe = __fb + __bs;
     }
     if (__fb <= __vt && __vt < __fe)
-      __vt = (const_iterator(static_cast<__map_const_pointer>(__f.__m_iter_), __vt) += __r - __f).__ptr_;
+      __vt = (const_iterator(__f.__m_iter_, __vt) += __r - __f).__ptr_;
     for (; __fb != __fe; ++__fb, ++__r, ++__size())
       __alloc_traits::construct(__a, std::addressof(*__r), std::move(*__fb));
     __n -= __bs;
@@ -2305,7 +2316,7 @@ void deque<_Tp, _Allocator>::__move_construct_backward_and_check(
       __lb = __le - __bs;
     }
     if (__lb <= __vt && __vt < __le)
-      __vt = (const_iterator(static_cast<__map_const_pointer>(__l.__m_iter_), __vt) -= __l - __r + 1).__ptr_;
+      __vt = (const_iterator(__l.__m_iter_, __vt) -= __l - __r + 1).__ptr_;
     while (__le != __lb) {
       __alloc_traits::construct(__a, std::addressof(*--__r), std::move(*--__le));
       --__start_;


        


More information about the libcxx-commits mailing list