[libcxx-commits] [libcxx] [libc++] Assert that the position passed to insert and emplace is in [begin(), end()] (PR #230490)

Ilyas Mallah via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 13 03:21:40 PDT 2026


https://github.com/ilyas-mallah updated https://github.com/llvm/llvm-project/pull/230490

>From 1e950db3ba658a9a4ede6e821e29392ff6d922f6 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 02:03:00 +0200
Subject: [PATCH 1/7] [libc++] Assert that the vector insert and emplace
 position is in [begin(), end()]

---
 libcxx/include/__vector/vector.h              | 15 ++++
 .../assert.insert.invalid_position.pass.cpp   | 82 +++++++++++++++++++
 2 files changed, 97 insertions(+)
 create mode 100644 libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp

diff --git a/libcxx/include/__vector/vector.h b/libcxx/include/__vector/vector.h
index aa8441e3f9554..92c559d49747d 100644
--- a/libcxx/include/__vector/vector.h
+++ b/libcxx/include/__vector/vector.h
@@ -766,6 +766,13 @@ class _LIBCPP_WARN_UNUSED vector {
 #endif // _LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR
   }
 
+  _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI bool
+  __is_valid_insert_position(const_iterator __position) const _NOEXCEPT {
+    auto __pos = std::__to_address(__position);
+    auto __end = std::__to_address(__layout_.__end_ptr());
+    return std::__is_pointer_in_range(std::__to_address(__layout_.__begin_ptr()), __end, __pos) || __pos == __end;
+  }
+
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI void
   __move_range(pointer __from_s, pointer __from_e, pointer __to);
 
@@ -1110,6 +1117,8 @@ template <class _Tp, class _Allocator>
 template <class... _Args>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector::insert/emplace called with an iterator outside [begin(), end()]");
   pointer __p = this->__layout_.__begin_ptr() + (__position - begin());
   if (size() != capacity()) {
     pointer __end = __layout_.__end_ptr();
@@ -1131,6 +1140,8 @@ vector<_Tp, _Allocator>::emplace(const_iterator __position, _Args&&... __args) {
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::insert(const_iterator __position, size_type __n, const_reference __x) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector::insert/emplace called with an iterator outside [begin(), end()]");
   pointer __p = this->__layout_.__begin_ptr() + (__position - begin());
   if (__n > 0) {
     if (__n <= __layout_.__remaining_capacity()) {
@@ -1162,6 +1173,8 @@ template <class _Tp, class _Allocator>
 template <class _InputIterator, class _Sentinel>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::__insert_with_sentinel(const_iterator __position, _InputIterator __first, _Sentinel __last) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector::insert/emplace called with an iterator outside [begin(), end()]");
   difference_type __off = __position - begin();
   pointer __p           = this->__layout_.__begin_ptr() + __off;
   pointer __old_last    = __layout_.__end_ptr();
@@ -1203,6 +1216,8 @@ template <class _Tp, class _Allocator>
 template <class _AlgPolicy, class _Iterator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::__insert_with_size(const_iterator __position, _Iterator __first, difference_type __n) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector::insert/emplace called with an iterator outside [begin(), end()]");
   pointer __p = this->__layout_.__begin_ptr() + (__position - begin());
   if (__n > 0) {
     if (__n <= static_cast<difference_type>(__layout_.__remaining_capacity())) {
diff --git a/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp
new file mode 100644
index 0000000000000..c9c8a6b41d6bc
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <vector>
+
+// Make sure insert and emplace catch a position that isn't in [begin(), end()], such as an iterator
+// into another vector or an iterator invalidated by a reallocation.
+
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
+#include <vector>
+
+#include "check_assertion.h"
+#include "min_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Vector>
+void test_invalid_position(Vector& v, typename Vector::const_iterator pos) {
+  [[maybe_unused]] const char* message = "vector::insert/emplace called with an iterator outside [begin(), end()]";
+  int a[]                              = {1, 2, 3};
+  const int x                          = 4;
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, x), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, 4), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, 2, x), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, a, a + 3), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, forward_iterator<int*>(a), forward_iterator<int*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE((v.insert(pos, {1, 2, 3})), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.emplace(pos, 4), message);
+#if TEST_STD_VER >= 23
+  TEST_LIBCPP_ASSERT_FAILURE((v.insert_range(pos, std::vector<int>{1, 2, 3})), message);
+#endif
+}
+
+template <class Vector>
+void test() {
+  // An iterator into another vector.
+  {
+    Vector v     = {1, 2, 3};
+    Vector other = {4, 5, 6};
+    test_invalid_position(v, other.begin());
+    test_invalid_position(v, other.end());
+  }
+
+  // An iterator into another vector, inserting into an empty one.
+  {
+    Vector v;
+    Vector other = {4, 5, 6};
+    test_invalid_position(v, other.begin());
+  }
+
+  // An iterator invalidated by a reallocation.
+  {
+    Vector v                              = {1, 2, 3};
+    typename Vector::const_iterator stale = v.begin();
+    v.reserve(v.capacity() + 1);
+    test_invalid_position(v, stale);
+  }
+
+  // An iterator past end() that is still inside the allocation.
+  {
+    Vector v = {1, 2, 3};
+    v.reserve(10);
+    typename Vector::const_iterator stale = v.end();
+    v.pop_back();
+    test_invalid_position(v, stale);
+  }
+}
+
+int main(int, char**) {
+  test<std::vector<int> >();
+  test<std::vector<int, min_allocator<int> > >();
+
+  return 0;
+}

>From 0d5a3060ead2fce7d271885d3e33c955a9b85d9a Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 02:03:01 +0200
Subject: [PATCH 2/7] [libc++] Assert that the vector<bool> insert and emplace
 position is in [begin(), end()]

---
 libcxx/include/__vector/vector_bool.h         | 13 +++
 .../assert.insert.invalid_position.pass.cpp   | 82 +++++++++++++++++++
 2 files changed, 95 insertions(+)
 create mode 100644 libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp

diff --git a/libcxx/include/__vector/vector_bool.h b/libcxx/include/__vector/vector_bool.h
index 12938a90be3aa..c6f306cdce8a9 100644
--- a/libcxx/include/__vector/vector_bool.h
+++ b/libcxx/include/__vector/vector_bool.h
@@ -453,6 +453,11 @@ class _LIBCPP_WARN_UNUSED vector<bool, _Allocator> {
   _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI iterator
   __insert_with_size(const_iterator __position, _Iterator __first, _Sentinel __last, difference_type __n);
 
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
+  __is_valid_insert_position(const_iterator __position) const _NOEXCEPT {
+    return begin() <= __position && __position <= end();
+  }
+
   //  Allocate space for __n objects
   //  throws length_error if __n > max_size()
   //  throws (probably bad_alloc) if memory run out
@@ -895,6 +900,8 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 void vector<bool, _Allocator>::push_back(const val
 template <class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<bool, _Allocator>::iterator
 vector<bool, _Allocator>::insert(const_iterator __position, const value_type& __x) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector<bool>::insert/emplace called with an iterator outside [begin(), end()]");
   iterator __r;
   if (size() < capacity()) {
     const_iterator __old_end = end();
@@ -916,6 +923,8 @@ vector<bool, _Allocator>::insert(const_iterator __position, const value_type& __
 template <class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<bool, _Allocator>::iterator
 vector<bool, _Allocator>::insert(const_iterator __position, size_type __n, const value_type& __x) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector<bool>::insert/emplace called with an iterator outside [begin(), end()]");
   iterator __r;
   size_type __c = capacity();
   if (__n <= __c && size() <= __c - __n) {
@@ -946,6 +955,8 @@ template <class _Allocator>
 template <class _InputIterator, class _Sentinel>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI typename vector<bool, _Allocator>::iterator
 vector<bool, _Allocator>::__insert_with_sentinel(const_iterator __position, _InputIterator __first, _Sentinel __last) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector<bool>::insert/emplace called with an iterator outside [begin(), end()]");
   difference_type __off = __position - begin();
   iterator __p          = __const_iterator_cast(__position);
   iterator __old_end    = end();
@@ -981,6 +992,8 @@ template <class _Iterator, class _Sentinel>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI typename vector<bool, _Allocator>::iterator
 vector<bool, _Allocator>::__insert_with_size(
     const_iterator __position, _Iterator __first, _Sentinel __last, difference_type __n_signed) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__is_valid_insert_position(__position),
+                                      "vector<bool>::insert/emplace called with an iterator outside [begin(), end()]");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__n_signed >= 0, "invalid range specified");
   const size_type __n = static_cast<size_type>(__n_signed);
   iterator __r;
diff --git a/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
new file mode 100644
index 0000000000000..e50a868c97267
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <vector>
+
+// Make sure vector<bool>::insert and emplace catch a position that isn't in [begin(), end()], such as
+// an iterator into another vector or an iterator invalidated by a reallocation.
+
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
+#include <vector>
+
+#include "check_assertion.h"
+#include "min_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Vector>
+void test_invalid_position(Vector& v, typename Vector::const_iterator pos) {
+  [[maybe_unused]] const char* message =
+      "vector<bool>::insert/emplace called with an iterator outside [begin(), end()]";
+  bool a[]     = {true, false, true};
+  const bool x = true;
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, x), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, 2, x), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, a, a + 3), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.insert(pos, forward_iterator<bool*>(a), forward_iterator<bool*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      v.insert(pos, cpp17_input_iterator<bool*>(a), cpp17_input_iterator<bool*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE((v.insert(pos, {true, false, true})), message);
+  TEST_LIBCPP_ASSERT_FAILURE(v.emplace(pos, true), message);
+#if TEST_STD_VER >= 23
+  TEST_LIBCPP_ASSERT_FAILURE((v.insert_range(pos, std::vector<bool>{true, false, true})), message);
+#endif
+}
+
+template <class Vector>
+void test() {
+  // An iterator into another vector.
+  {
+    Vector v     = {true, false, true};
+    Vector other = {false, true, false};
+    test_invalid_position(v, other.begin());
+    test_invalid_position(v, other.end());
+  }
+
+  // An iterator into another vector, inserting into an empty one.
+  {
+    Vector v;
+    Vector other = {false, true, false};
+    test_invalid_position(v, other.begin());
+  }
+
+  // An iterator invalidated by a reallocation.
+  {
+    Vector v                              = {true, false, true};
+    typename Vector::const_iterator stale = v.begin();
+    v.reserve(v.capacity() + 1);
+    test_invalid_position(v, stale);
+  }
+
+  // An iterator past end() that is still inside the allocation.
+  {
+    Vector v                              = {true, false, true};
+    typename Vector::const_iterator stale = v.end();
+    v.pop_back();
+    test_invalid_position(v, stale);
+  }
+}
+
+int main(int, char**) {
+  test<std::vector<bool> >();
+  test<std::vector<bool, min_allocator<bool> > >();
+
+  return 0;
+}

>From 07b7053af8b5d1a149ffd08a49c85fe591355dda Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 02:03:02 +0200
Subject: [PATCH 3/7] [libc++] Assert that the string insert position is in
 [begin(), end()]

---
 libcxx/include/string                         | 10 +++
 .../assert.insert.invalid_position.pass.cpp   | 86 +++++++++++++++++++
 2 files changed, 96 insertions(+)
 create mode 100644 libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp

diff --git a/libcxx/include/string b/libcxx/include/string
index a27f9344916d1..6b73cf109def3 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -1567,6 +1567,8 @@ public:
 
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 iterator
   insert(const_iterator __pos, size_type __n, value_type __c) {
+    _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+        __is_valid_insert_position(__pos), "string::insert called with an iterator outside [begin(), end()]");
     difference_type __p = __pos - begin();
     insert(static_cast<size_type>(__p), __n, __c);
     return begin() + __p;
@@ -2477,6 +2479,10 @@ private:
     return std::__is_pointer_in_range(data(), data() + size() + 1, std::addressof(__v));
   }
 
+  _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool __is_valid_insert_position(const_iterator __pos) const {
+    return std::__is_pointer_in_range(data(), data() + size() + 1, std::__to_address(__pos));
+  }
+
   [[__noreturn__]] _LIBCPP_HIDE_FROM_ABI static void __throw_length_error() {
     std::__throw_length_error("basic_string");
   }
@@ -3144,6 +3150,8 @@ template <class _Iterator, class _Sentinel>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::__insert_with_size(
     const_iterator __pos, _Iterator __first, _Sentinel __last, size_type __n) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __is_valid_insert_position(__pos), "string::insert called with an iterator outside [begin(), end()]");
   size_type __ip = static_cast<size_type>(__pos - begin());
   if (__n == 0)
     return begin() + __ip;
@@ -3176,6 +3184,8 @@ basic_string<_CharT, _Traits, _Allocator>::insert(size_type __pos, const value_t
 template <class _CharT, class _Traits, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::insert(const_iterator __pos, value_type __c) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __is_valid_insert_position(__pos), "string::insert called with an iterator outside [begin(), end()]");
   size_type __ip  = static_cast<size_type>(__pos - begin());
   size_type __sz  = size();
   size_type __cap = capacity();
diff --git a/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp
new file mode 100644
index 0000000000000..3a1e4b052b861
--- /dev/null
+++ b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <string>
+
+// Make sure insert catches a position that isn't in [begin(), end()], such as an iterator into
+// another string or an iterator invalidated by a reallocation.
+
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
+// std::string::insert(const_iterator, char) is instantiated in the dylib, so we need an up-to-date one
+// XFAIL: using-built-library-before-llvm-24
+
+#include <string>
+
+#include "check_assertion.h"
+#include "min_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+#if TEST_STD_VER >= 23
+#  include <string_view>
+#endif
+
+template <class String>
+void test_invalid_position(String& s, typename String::const_iterator pos) {
+  [[maybe_unused]] const char* message = "string::insert called with an iterator outside [begin(), end()]";
+  const char a[]                       = "abc";
+  TEST_LIBCPP_ASSERT_FAILURE(s.insert(pos, 'x'), message);
+  TEST_LIBCPP_ASSERT_FAILURE(s.insert(pos, 2, 'x'), message);
+  TEST_LIBCPP_ASSERT_FAILURE(s.insert(pos, a, a + 3), message);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      s.insert(pos, forward_iterator<const char*>(a), forward_iterator<const char*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE(
+      s.insert(pos, cpp17_input_iterator<const char*>(a), cpp17_input_iterator<const char*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE((s.insert(pos, {'a', 'b', 'c'})), message);
+#if TEST_STD_VER >= 23
+  TEST_LIBCPP_ASSERT_FAILURE(s.insert_range(pos, std::string_view("abc")), message);
+#endif
+}
+
+template <class String>
+void test() {
+  // An iterator into another string, for short and long strings.
+  {
+    String s("123");
+    String other("456");
+    test_invalid_position(s, other.begin());
+    test_invalid_position(s, other.end());
+  }
+  {
+    String s("a string that is too long for the small buffer");
+    String other("another string that is too long for the small buffer");
+    test_invalid_position(s, other.begin());
+    test_invalid_position(s, other.end());
+  }
+
+  // An iterator invalidated by a reallocation.
+  {
+    String s("a string that is too long for the small buffer");
+    typename String::const_iterator stale = s.begin();
+    s.reserve(s.capacity() + 1);
+    test_invalid_position(s, stale);
+  }
+
+  // An iterator past end() that is still inside the allocation.
+  {
+    String s("123");
+    typename String::const_iterator stale = s.end();
+    s.pop_back();
+    test_invalid_position(s, stale);
+  }
+}
+
+int main(int, char**) {
+  test<std::string>();
+  test<std::basic_string<char, std::char_traits<char>, min_allocator<char> > >();
+
+  return 0;
+}

>From 886a02c9f2ef11898fe2620847bf93a589c3e823 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 02:03:03 +0200
Subject: [PATCH 4/7] [libc++] Assert that the deque insert and emplace
 position is in [begin(), end()]

---
 libcxx/include/deque                          | 10 +++
 .../assert.insert.invalid_position.pass.cpp   | 82 +++++++++++++++++++
 2 files changed, 92 insertions(+)
 create mode 100644 libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp

diff --git a/libcxx/include/deque b/libcxx/include/deque
index ba4d00f81e95e..c790986537969 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -1243,6 +1243,10 @@ private:
   template <class _BiIter>
   _LIBCPP_HIDE_FROM_ABI iterator __insert_bidirectional(const_iterator __p, _BiIter __f, _BiIter __l, size_type __n);
 
+  _LIBCPP_HIDE_FROM_ABI bool __is_valid_insert_position(const_iterator __p) const {
+    return begin() <= __p && __p <= end();
+  }
+
   template <class _InpIter, __enable_if_t<__has_exactly_input_iterator_category<_InpIter>::value, int> = 0>
   _LIBCPP_HIDE_FROM_ABI void __append(_InpIter __f, _InpIter __l);
   template <class _ForIter, __enable_if_t<__has_forward_iterator_category<_ForIter>::value, int> = 0>
@@ -1683,6 +1687,8 @@ deque<_Tp, _Allocator>::emplace_front(_Args&&... __args) {
 template <class _Tp, class _Allocator>
 template <class... _Args>
 typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::__emplace(const_iterator __p, _Args&&... __args) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __is_valid_insert_position(__p), "deque::insert/emplace called with an iterator outside [begin(), end()]");
   size_type __pos     = __p - begin();
   size_type __to_end  = size() - __pos;
   allocator_type& __a = __alloc();
@@ -1732,6 +1738,8 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::__emplace(cons
 template <class _Tp, class _Allocator>
 typename deque<_Tp, _Allocator>::iterator
 deque<_Tp, _Allocator>::insert(const_iterator __p, size_type __n, const value_type& __v) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __is_valid_insert_position(__p), "deque::insert/emplace called with an iterator outside [begin(), end()]");
   size_type __pos     = __p - begin();
   size_type __to_end  = __size() - __pos;
   allocator_type& __a = __alloc();
@@ -1832,6 +1840,8 @@ template <class _Tp, class _Allocator>
 template <class _BiIter>
 _LIBCPP_HIDE_FROM_ABI typename deque<_Tp, _Allocator>::iterator
 deque<_Tp, _Allocator>::__insert_bidirectional(const_iterator __p, _BiIter __f, _BiIter __l, size_type __n) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __is_valid_insert_position(__p), "deque::insert/emplace called with an iterator outside [begin(), end()]");
   size_type __pos     = __p - begin();
   size_type __to_end  = size() - __pos;
   allocator_type& __a = __alloc();
diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
new file mode 100644
index 0000000000000..c40ddb5c54600
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// <deque>
+
+// Make sure insert and emplace catch a position that isn't in [begin(), end()], such as an iterator
+// into another deque or an iterator to an element that was removed.
+
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
+#include <deque>
+#include <vector>
+
+#include "check_assertion.h"
+#include "min_allocator.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Deque>
+void test_invalid_position(Deque& d, typename Deque::const_iterator pos) {
+  [[maybe_unused]] const char* message = "deque::insert/emplace called with an iterator outside [begin(), end()]";
+  int a[]                              = {1, 2, 3};
+  const int x                          = 4;
+  TEST_LIBCPP_ASSERT_FAILURE(d.insert(pos, x), message);
+  TEST_LIBCPP_ASSERT_FAILURE(d.insert(pos, 4), message);
+  TEST_LIBCPP_ASSERT_FAILURE(d.insert(pos, 2, x), message);
+  TEST_LIBCPP_ASSERT_FAILURE(d.insert(pos, a, a + 3), message);
+  TEST_LIBCPP_ASSERT_FAILURE(d.insert(pos, forward_iterator<int*>(a), forward_iterator<int*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE(d.insert(pos, cpp17_input_iterator<int*>(a), cpp17_input_iterator<int*>(a + 3)), message);
+  TEST_LIBCPP_ASSERT_FAILURE((d.insert(pos, {1, 2, 3})), message);
+  TEST_LIBCPP_ASSERT_FAILURE(d.emplace(pos, 4), message);
+#if TEST_STD_VER >= 23
+  TEST_LIBCPP_ASSERT_FAILURE((d.insert_range(pos, std::vector<int>{1, 2, 3})), message);
+#endif
+}
+
+template <class Deque>
+void test() {
+  // An iterator into another deque.
+  {
+    Deque d     = {1, 2, 3};
+    Deque other = {4, 5, 6};
+    test_invalid_position(d, other.begin());
+    test_invalid_position(d, other.end());
+  }
+
+  // An iterator into another deque, inserting into an empty one.
+  {
+    Deque d;
+    Deque other = {4, 5, 6};
+    test_invalid_position(d, other.begin());
+  }
+
+  // An iterator to an element removed by pop_front().
+  {
+    Deque d                              = {1, 2, 3};
+    typename Deque::const_iterator stale = d.begin();
+    d.pop_front();
+    test_invalid_position(d, stale);
+  }
+
+  // An iterator past end() after pop_back().
+  {
+    Deque d                              = {1, 2, 3};
+    typename Deque::const_iterator stale = d.end();
+    d.pop_back();
+    test_invalid_position(d, stale);
+  }
+}
+
+int main(int, char**) {
+  test<std::deque<int> >();
+  test<std::deque<int, min_allocator<int> > >();
+
+  return 0;
+}

>From 67421996734d82be1cd44ac3774bb122abebf76e Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Fri, 9 Oct 2026 21:24:01 +0200
Subject: [PATCH 5/7] [libc++] Reorder test file headers

---
 .../deque/assert.insert.invalid_position.pass.cpp      |  6 +++---
 .../assert.insert.invalid_position.pass.cpp            |  6 +++---
 .../vector/assert.insert.invalid_position.pass.cpp     |  6 +++---
 .../assert.insert.invalid_position.pass.cpp            | 10 +++++-----
 4 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
index c40ddb5c54600..f9ef36637af42 100644
--- a/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
@@ -6,14 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
 // <deque>
 
 // Make sure insert and emplace catch a position that isn't in [begin(), end()], such as an iterator
 // into another deque or an iterator to an element that was removed.
 
-// REQUIRES: can-test-hardening-assertions-fast
-// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
-
 #include <deque>
 #include <vector>
 
diff --git a/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
index e50a868c97267..935962da35cc1 100644
--- a/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
@@ -6,14 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
 // <vector>
 
 // Make sure vector<bool>::insert and emplace catch a position that isn't in [begin(), end()], such as
 // an iterator into another vector or an iterator invalidated by a reallocation.
 
-// REQUIRES: can-test-hardening-assertions-fast
-// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
-
 #include <vector>
 
 #include "check_assertion.h"
diff --git a/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp
index c9c8a6b41d6bc..0a6eca345bf5d 100644
--- a/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/vector/assert.insert.invalid_position.pass.cpp
@@ -6,14 +6,14 @@
 //
 //===----------------------------------------------------------------------===//
 
+// REQUIRES: can-test-hardening-assertions-fast
+// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
+
 // <vector>
 
 // Make sure insert and emplace catch a position that isn't in [begin(), end()], such as an iterator
 // into another vector or an iterator invalidated by a reallocation.
 
-// REQUIRES: can-test-hardening-assertions-fast
-// UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
-
 #include <vector>
 
 #include "check_assertion.h"
diff --git a/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp
index 3a1e4b052b861..159c4772bd869 100644
--- a/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp
+++ b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.insert.invalid_position.pass.cpp
@@ -6,17 +6,17 @@
 //
 //===----------------------------------------------------------------------===//
 
-// <string>
-
-// Make sure insert catches a position that isn't in [begin(), end()], such as an iterator into
-// another string or an iterator invalidated by a reallocation.
-
 // REQUIRES: can-test-hardening-assertions-fast
 // UNSUPPORTED: libcpp-assertion-semantic={{ignore|observe}}
 
 // std::string::insert(const_iterator, char) is instantiated in the dylib, so we need an up-to-date one
 // XFAIL: using-built-library-before-llvm-24
 
+// <string>
+
+// Make sure insert catches a position that isn't in [begin(), end()], such as an iterator into
+// another string or an iterator invalidated by a reallocation.
+
 #include <string>
 
 #include "check_assertion.h"

>From 862260a70573527eb01fa20a981cba9194c3d761 Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Tue, 13 Oct 2026 10:38:03 +0200
Subject: [PATCH 6/7] [libc++] Use __is_pointer_in_range for the vector<bool>
 insert position check

---
 libcxx/include/__vector/vector_bool.h         |  8 ++++-
 .../assert.insert.invalid_position.pass.cpp   | 33 +++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/libcxx/include/__vector/vector_bool.h b/libcxx/include/__vector/vector_bool.h
index c6f306cdce8a9..a5de637d2b692 100644
--- a/libcxx/include/__vector/vector_bool.h
+++ b/libcxx/include/__vector/vector_bool.h
@@ -47,6 +47,7 @@
 #include <__utility/exception_guard.h>
 #include <__utility/exchange.h>
 #include <__utility/forward.h>
+#include <__utility/is_pointer_in_range.h>
 #include <__utility/move.h>
 #include <__utility/swap.h>
 #include <climits>
@@ -455,7 +456,12 @@ class _LIBCPP_WARN_UNUSED vector<bool, _Allocator> {
 
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 bool
   __is_valid_insert_position(const_iterator __position) const _NOEXCEPT {
-    return begin() <= __position && __position <= end();
+    // end() may point one word past the storage, so its word is compared for equality.
+    auto __first = std::__to_address(__begin_);
+    auto __last  = __first + __size_ / __bits_per_word;
+    auto __seg   = std::__to_address(__position.__seg_);
+    return std::__is_pointer_in_range(__first, __last, __seg) ||
+           (__seg == __last && __position.__ctz_ <= __size_ % __bits_per_word);
   }
 
   //  Allocate space for __n objects
diff --git a/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
index 935962da35cc1..c9900fbf57cbe 100644
--- a/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/vector.bool/assert.insert.invalid_position.pass.cpp
@@ -14,6 +14,8 @@
 // Make sure vector<bool>::insert and emplace catch a position that isn't in [begin(), end()], such as
 // an iterator into another vector or an iterator invalidated by a reallocation.
 
+#include <cassert>
+#include <cstddef>
 #include <vector>
 
 #include "check_assertion.h"
@@ -72,11 +74,42 @@ void test() {
     v.pop_back();
     test_invalid_position(v, stale);
   }
+
+  // An iterator into another vector, inserting into a full vector whose end() is one word past its storage.
+  {
+    Vector v;
+    v.reserve(1);
+    v.resize(v.capacity());
+    Vector other = {false, true, false};
+    test_invalid_position(v, other.begin());
+  }
+}
+
+// Every position in [begin(), end()] is accepted, including end() of a full vector.
+template <class Vector>
+TEST_CONSTEXPR_CXX20 bool test_valid_positions() {
+  Vector v;
+  v.reserve(1);
+  v.resize(v.capacity());
+  for (std::size_t i = 0; i <= v.size(); ++i) {
+    Vector w = v;
+    w.insert(w.cbegin() + i, true);
+    assert(w.size() == v.size() + 1);
+    assert(w[i]);
+  }
+  return true;
 }
 
 int main(int, char**) {
   test<std::vector<bool> >();
   test<std::vector<bool, min_allocator<bool> > >();
 
+  test_valid_positions<std::vector<bool> >();
+  test_valid_positions<std::vector<bool, min_allocator<bool> > >();
+#if TEST_STD_VER >= 20
+  static_assert(test_valid_positions<std::vector<bool> >());
+  static_assert(test_valid_positions<std::vector<bool, min_allocator<bool> > >());
+#endif
+
   return 0;
 }

>From c5af97ea9dc5b82296a045a56d093fd2fad1adfc Mon Sep 17 00:00:00 2001
From: Ilyas Mallah <github at ilyasm.dev>
Date: Tue, 13 Oct 2026 10:38:03 +0200
Subject: [PATCH 7/7] [libc++] Check the deque insert position against the map
 and block

---
 libcxx/include/deque                           | 15 ++++++++++++++-
 .../assert.insert.invalid_position.pass.cpp    | 18 ++++++++++++++++++
 2 files changed, 32 insertions(+), 1 deletion(-)

diff --git a/libcxx/include/deque b/libcxx/include/deque
index c790986537969..d1f03080712c9 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -234,6 +234,7 @@ template <class T, class Allocator, class Predicate>
 #  include <__type_traits/type_identity.h>
 #  include <__utility/exception_guard.h>
 #  include <__utility/forward.h>
+#  include <__utility/is_pointer_in_range.h>
 #  include <__utility/move.h>
 #  include <__utility/pair.h>
 #  include <__utility/swap.h>
@@ -1244,7 +1245,19 @@ private:
   _LIBCPP_HIDE_FROM_ABI iterator __insert_bidirectional(const_iterator __p, _BiIter __f, _BiIter __l, size_type __n);
 
   _LIBCPP_HIDE_FROM_ABI bool __is_valid_insert_position(const_iterator __p) const {
-    return begin() <= __p && __p <= end();
+    // Check that __p points into our map and blocks before computing its index from them.
+    if (__map_.empty())
+      return __p == end();
+    auto __map_first = std::__to_address(__map_.begin());
+    auto __slot      = std::__to_address(__p.__m_iter_);
+    if (!std::__is_pointer_in_range(__map_first, std::__to_address(__map_.end()), __slot))
+      return false;
+    auto __block = std::__to_address(*__slot);
+    auto __elem  = std::__to_address(__p.__ptr_);
+    if (!std::__is_pointer_in_range(__block, __block + __block_size, __elem))
+      return false;
+    auto __pos = static_cast<size_type>((__slot - __map_first) * __block_size + (__elem - __block));
+    return __start_ <= __pos && __pos <= __start_ + size();
   }
 
   template <class _InpIter, __enable_if_t<__has_exactly_input_iterator_category<_InpIter>::value, int> = 0>
diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
index f9ef36637af42..5d5b5d01940bb 100644
--- a/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.insert.invalid_position.pass.cpp
@@ -72,6 +72,24 @@ void test() {
     d.pop_back();
     test_invalid_position(d, stale);
   }
+
+  // An iterator into another deque, inserting into an empty deque that still holds a block.
+  {
+    Deque d = {1, 2, 3};
+    d.clear();
+    Deque other = {4, 5, 6};
+    test_invalid_position(d, other.begin());
+    test_invalid_position(d, other.end());
+  }
+
+  // An iterator into a block that pop_front() released.
+  {
+    Deque d(3 * 4096);
+    typename Deque::const_iterator stale = d.begin();
+    for (int i = 0; i != 2 * 4096; ++i)
+      d.pop_front();
+    test_invalid_position(d, stale);
+  }
 }
 
 int main(int, char**) {



More information about the libcxx-commits mailing list