[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