[libcxx-commits] [libcxx] [libc++] Check iterator ownership in erase() for string and vector (PR #90919)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jul 9 12:30:08 PDT 2024


https://github.com/ldionne updated https://github.com/llvm/llvm-project/pull/90919

>From e3ad6fcbc8c13511c455da2dcad4c716875c365b Mon Sep 17 00:00:00 2001
From: Christopher Di Bella <cjdb at google.com>
Date: Thu, 2 May 2024 23:56:48 +0000
Subject: [PATCH 1/4] [libcxx] adds additional checks to RAI containers'
 `erase`

This ensures that the iterators passed in are in-range for the
container. Thanks to @mclow for identifying this opportunity.
---
 libcxx/include/deque  | 11 ++++++++---
 libcxx/include/string |  8 +++++++-
 libcxx/include/vector | 11 ++++++++---
 3 files changed, 23 insertions(+), 7 deletions(-)

diff --git a/libcxx/include/deque b/libcxx/include/deque
index 4fc994a6e229b..48e732208263b 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -2380,7 +2380,9 @@ void deque<_Tp, _Allocator>::__move_construct_backward_and_check(
 template <class _Tp, class _Allocator>
 typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f) {
   _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __f != end(), "deque::erase(iterator) called with a non-dereferenceable iterator");
+      __f >= begin(), "deque::erase(iterator) called with an iterator outside of the container's control");
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __f < end(), "deque::erase(iterator) called with an iterator outside of the container's control");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
   iterator __b          = begin();
@@ -2406,6 +2408,10 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_it
 
 template <class _Tp, class _Allocator>
 typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f, const_iterator __l) {
+  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+      __f <= begin(), "deque::erase(first, last) called with an iterator range starting before 'begin()'");
+  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+      __l <= end(), "deque::erase(first, last) called with an iterator range finishing after 'end()'");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__f <= __l, "deque::erase(first, last) called with an invalid range");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
@@ -2530,8 +2536,7 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator<=(const deque<_Tp, _Allocator>& __x,
 template <class _Tp, class _Allocator>
 _LIBCPP_HIDE_FROM_ABI __synth_three_way_result<_Tp>
 operator<=>(const deque<_Tp, _Allocator>& __x, const deque<_Tp, _Allocator>& __y) {
-  return std::lexicographical_compare_three_way(
-      __x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
+  return std::lexicographical_compare_three_way(__x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
 }
 
 #endif // _LIBCPP_STD_VER <= 17
diff --git a/libcxx/include/string b/libcxx/include/string
index 9a52ab6aef41e..306d3c8227fd9 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -3166,7 +3166,9 @@ template <class _CharT, class _Traits, class _Allocator>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::erase(const_iterator __pos) {
   _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __pos != end(), "string::erase(iterator) called with a non-dereferenceable iterator");
+      __pos >= begin(), "string::erase(iterator) called with an iterator outside of the container's control");
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __pos < end(), "string::erase(iterator) called with an iterator outside of the container's control");
   iterator __b  = begin();
   size_type __r = static_cast<size_type>(__pos - __b);
   erase(__r, 1);
@@ -3176,6 +3178,10 @@ basic_string<_CharT, _Traits, _Allocator>::erase(const_iterator __pos) {
 template <class _CharT, class _Traits, class _Allocator>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::erase(const_iterator __first, const_iterator __last) {
+  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+      __first >= begin(), "string::erase(first, last) called with an iterator range starting before 'begin()'");
+  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+      __last <= end(), "string::erase(first, last) called with an iterator range finishing after 'end()'");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "string::erase(first, last) called with invalid range");
   iterator __b  = begin();
   size_type __r = static_cast<size_type>(__first - __b);
diff --git a/libcxx/include/vector b/libcxx/include/vector
index aaf51d18fe30f..de1e202fa57d7 100644
--- a/libcxx/include/vector
+++ b/libcxx/include/vector
@@ -1534,7 +1534,9 @@ template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::erase(const_iterator __position) {
   _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __position != end(), "vector::erase(iterator) called with a non-dereferenceable iterator");
+      __position >= begin(), "vector::erase(iterator) called with an iterator outside of the container's control");
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      __position < end(), "vector::erase(iterator) called with an iterator outside of the container's control");
   difference_type __ps = __position - cbegin();
   pointer __p          = this->__begin_ + __ps;
   this->__destruct_at_end(std::move(__p + 1, this->__end_, __p));
@@ -1544,6 +1546,10 @@ vector<_Tp, _Allocator>::erase(const_iterator __position) {
 template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
+  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+      __first >= begin(), "vector::erase(first, last) called with an iterator range starting before 'begin()'");
+  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
+      __last <= end(), "vector::erase(first, last) called with an iterator range finishing after 'end()'");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
   pointer __p = this->__begin_ + (__first - begin());
   if (__first != __last) {
@@ -2902,8 +2908,7 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator<=(const vector<_Tp, _Allocator>& __x,
 template <class _Tp, class _Allocator>
 _LIBCPP_HIDE_FROM_ABI constexpr __synth_three_way_result<_Tp>
 operator<=>(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) {
-  return std::lexicographical_compare_three_way(
-      __x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
+  return std::lexicographical_compare_three_way(__x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
 }
 
 #endif // _LIBCPP_STD_VER <= 17

>From 555564113686fb168e3459b1686540876d90e95c Mon Sep 17 00:00:00 2001
From: Christopher Di Bella <cjdb.ns at gmail.com>
Date: Mon, 10 Jun 2024 10:07:55 -0700
Subject: [PATCH 2/4] Update libcxx/include/deque

Co-authored-by: Louis Dionne <ldionne.2 at gmail.com>
---
 libcxx/include/deque | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/deque b/libcxx/include/deque
index 48e732208263b..28e2ca3b64f2a 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -2409,7 +2409,7 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_it
 template <class _Tp, class _Allocator>
 typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f, const_iterator __l) {
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __f <= begin(), "deque::erase(first, last) called with an iterator range starting before 'begin()'");
+      __f >= begin(), "deque::erase(first, last) called with an iterator range starting before 'begin()'");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(
       __l <= end(), "deque::erase(first, last) called with an iterator range finishing after 'end()'");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__f <= __l, "deque::erase(first, last) called with an invalid range");

>From 22f1a9820f533b44357866001a660fa3bd2dc409 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 9 Jul 2024 15:19:33 -0400
Subject: [PATCH 3/4] - Use __is_pointer_in_range for a non-UB comparison - Add
 tests - Downgrade checks we do in deque cause we can't detect iterator
 ownership for non-contiguous containers

---
 libcxx/include/deque                          | 11 +---
 libcxx/include/string                         | 12 ++--
 libcxx/include/vector                         | 15 +++--
 .../deque/assert.erase.iter.pass.cpp}         | 24 ++++----
 .../deque/assert.erase.iter_iter.pass.cpp     | 33 ++++++++++
 .../sequences/deque/assert.pass.cpp           |  4 --
 .../vector/assert.erase.iter.pass.cpp         | 43 +++++++++++++
 .../vector/assert.erase.iter_iter.pass.cpp    | 60 +++++++++++++++++++
 .../assert.erase.iter.pass.cpp                | 47 +++++++++++++++
 ...ss.cpp => assert.erase.iter_iter.pass.cpp} | 36 ++++++-----
 .../debug.erase.iter.pass.cpp                 | 35 -----------
 11 files changed, 233 insertions(+), 87 deletions(-)
 rename libcxx/test/libcxx/{strings/basic.string/string.modifiers/assert.erase_iter.null.pass.cpp => containers/sequences/deque/assert.erase.iter.pass.cpp} (59%)
 create mode 100644 libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter_iter.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter.pass.cpp
 create mode 100644 libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter_iter.pass.cpp
 create mode 100644 libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter.pass.cpp
 rename libcxx/test/libcxx/strings/basic.string/string.modifiers/{debug.erase.iter_iter.pass.cpp => assert.erase.iter_iter.pass.cpp} (50%)
 delete mode 100644 libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter.pass.cpp

diff --git a/libcxx/include/deque b/libcxx/include/deque
index 28e2ca3b64f2a..4fc994a6e229b 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -2380,9 +2380,7 @@ void deque<_Tp, _Allocator>::__move_construct_backward_and_check(
 template <class _Tp, class _Allocator>
 typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f) {
   _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __f >= begin(), "deque::erase(iterator) called with an iterator outside of the container's control");
-  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __f < end(), "deque::erase(iterator) called with an iterator outside of the container's control");
+      __f != end(), "deque::erase(iterator) called with a non-dereferenceable iterator");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
   iterator __b          = begin();
@@ -2408,10 +2406,6 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_it
 
 template <class _Tp, class _Allocator>
 typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f, const_iterator __l) {
-  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __f >= begin(), "deque::erase(first, last) called with an iterator range starting before 'begin()'");
-  _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __l <= end(), "deque::erase(first, last) called with an iterator range finishing after 'end()'");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__f <= __l, "deque::erase(first, last) called with an invalid range");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
@@ -2536,7 +2530,8 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator<=(const deque<_Tp, _Allocator>& __x,
 template <class _Tp, class _Allocator>
 _LIBCPP_HIDE_FROM_ABI __synth_three_way_result<_Tp>
 operator<=>(const deque<_Tp, _Allocator>& __x, const deque<_Tp, _Allocator>& __y) {
-  return std::lexicographical_compare_three_way(__x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
+  return std::lexicographical_compare_three_way(
+      __x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
 }
 
 #endif // _LIBCPP_STD_VER <= 17
diff --git a/libcxx/include/string b/libcxx/include/string
index 306d3c8227fd9..060d8733243ab 100644
--- a/libcxx/include/string
+++ b/libcxx/include/string
@@ -3166,9 +3166,8 @@ template <class _CharT, class _Traits, class _Allocator>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::erase(const_iterator __pos) {
   _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __pos >= begin(), "string::erase(iterator) called with an iterator outside of the container's control");
-  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __pos < end(), "string::erase(iterator) called with an iterator outside of the container's control");
+      std::__is_pointer_in_range(std::__to_address(begin()), std::__to_address(end()), std::__to_address(__pos)),
+      "string::erase(iterator) called with an iterator that isn't a valid iterator into this string");
   iterator __b  = begin();
   size_type __r = static_cast<size_type>(__pos - __b);
   erase(__r, 1);
@@ -3179,9 +3178,12 @@ template <class _CharT, class _Traits, class _Allocator>
 inline _LIBCPP_CONSTEXPR_SINCE_CXX20 typename basic_string<_CharT, _Traits, _Allocator>::iterator
 basic_string<_CharT, _Traits, _Allocator>::erase(const_iterator __first, const_iterator __last) {
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __first >= begin(), "string::erase(first, last) called with an iterator range starting before 'begin()'");
+      std::__is_pointer_in_range(std::__to_address(begin()), std::__to_address(end()), std::__to_address(__first)),
+      "string::erase(first, last) called with an iterator range that doesn't belong to this string");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __last <= end(), "string::erase(first, last) called with an iterator range finishing after 'end()'");
+      std::__is_pointer_in_range(std::__to_address(begin()), std::__to_address(end()), std::__to_address(__last)) ||
+          __last == end(),
+      "string::erase(first, last) called with a last iterator that doesn't fall within the string");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "string::erase(first, last) called with invalid range");
   iterator __b  = begin();
   size_type __r = static_cast<size_type>(__first - __b);
diff --git a/libcxx/include/vector b/libcxx/include/vector
index de1e202fa57d7..d5a24e6f59b95 100644
--- a/libcxx/include/vector
+++ b/libcxx/include/vector
@@ -1534,9 +1534,8 @@ template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 inline _LIBCPP_HIDE_FROM_ABI typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::erase(const_iterator __position) {
   _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __position >= begin(), "vector::erase(iterator) called with an iterator outside of the container's control");
-  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
-      __position < end(), "vector::erase(iterator) called with an iterator outside of the container's control");
+      std::__is_pointer_in_range(std::__to_address(begin()), std::__to_address(end()), std::__to_address(__position)),
+      "vector::erase(iterator) called with an iterator that isn't a valid iterator into this vector");
   difference_type __ps = __position - cbegin();
   pointer __p          = this->__begin_ + __ps;
   this->__destruct_at_end(std::move(__p + 1, this->__end_, __p));
@@ -1547,9 +1546,12 @@ template <class _Tp, class _Allocator>
 _LIBCPP_CONSTEXPR_SINCE_CXX20 typename vector<_Tp, _Allocator>::iterator
 vector<_Tp, _Allocator>::erase(const_iterator __first, const_iterator __last) {
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __first >= begin(), "vector::erase(first, last) called with an iterator range starting before 'begin()'");
+      std::__is_pointer_in_range(std::__to_address(begin()), std::__to_address(end()), std::__to_address(__first)),
+      "vector::erase(first, last) called with an iterator range that doesn't belong to this vector");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(
-      __last <= end(), "vector::erase(first, last) called with an iterator range finishing after 'end()'");
+      std::__is_pointer_in_range(std::__to_address(begin()), std::__to_address(end()), std::__to_address(__last)) ||
+          __last == end(),
+      "vector::erase(first, last) called with a last iterator that doesn't fall within the vector");
   _LIBCPP_ASSERT_VALID_INPUT_RANGE(__first <= __last, "vector::erase(first, last) called with invalid range");
   pointer __p = this->__begin_ + (__first - begin());
   if (__first != __last) {
@@ -2908,7 +2910,8 @@ inline _LIBCPP_HIDE_FROM_ABI bool operator<=(const vector<_Tp, _Allocator>& __x,
 template <class _Tp, class _Allocator>
 _LIBCPP_HIDE_FROM_ABI constexpr __synth_three_way_result<_Tp>
 operator<=>(const vector<_Tp, _Allocator>& __x, const vector<_Tp, _Allocator>& __y) {
-  return std::lexicographical_compare_three_way(__x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
+  return std::lexicographical_compare_three_way(
+      __x.begin(), __x.end(), __y.begin(), __y.end(), std::__synth_three_way);
 }
 
 #endif // _LIBCPP_STD_VER <= 17
diff --git a/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase_iter.null.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp
similarity index 59%
rename from libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase_iter.null.pass.cpp
rename to libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp
index 036e75965c488..5190bee4998b7 100644
--- a/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase_iter.null.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp
@@ -6,30 +6,28 @@
 //
 //===----------------------------------------------------------------------===//
 
-// <string>
+// <deque>
 
-// Call erase(const_iterator position) with end()
+// Make sure we catch invalid uses of std::deque::erase(iterator).
 
 // REQUIRES: has-unix-headers
 // UNSUPPORTED: c++03
 // UNSUPPORTED: libcpp-hardening-mode=none
 // XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
 
-#include <string>
+#include <deque>
 
 #include "check_assertion.h"
-#include "min_allocator.h"
-
-template <class S>
-void test() {
-  S l1("123");
-  typename S::const_iterator i = l1.end();
-  TEST_LIBCPP_ASSERT_FAILURE(l1.erase(i), "string::erase(iterator) called with a non-dereferenceable iterator");
-}
 
 int main(int, char**) {
-  test<std::string>();
-  test<std::basic_string<char, std::char_traits<char>, min_allocator<char> > >();
+  // With an invalid iterator
+  {
+    std::deque<int> v = {1, 2, 3, 4, 5};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v.erase(v.end()), "deque::erase(iterator) called with a non-dereferenceable iterator");
+  }
+
+  // Note that we currently can't catch misuse by erasing with an iterator from another container.
 
   return 0;
 }
diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter_iter.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter_iter.pass.cpp
new file mode 100644
index 0000000000000..e430f0ad0f962
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter_iter.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 we catch invalid uses of std::deque::erase(first, last).
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <deque>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  // Note that we currently can't catch misuse with using a range that belongs to another container.
+
+  // With an invalid range
+  {
+    std::deque<int> v = {1, 2, 3, 4, 5};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v.erase(v.begin() + 2, v.begin()), "deque::erase(first, last) called with an invalid range");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp
index 375a4cdcd58fe..9f332afe0e5df 100644
--- a/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp
@@ -48,9 +48,5 @@ int main(int, char**) {
     TEST_LIBCPP_ASSERT_FAILURE(cc[100], "deque::operator[] index out of bounds");
   }
 
-  TEST_LIBCPP_ASSERT_FAILURE(c.erase(c.end()), "deque::erase(iterator) called with a non-dereferenceable iterator");
-  TEST_LIBCPP_ASSERT_FAILURE(
-      c.erase(c.begin() + 1, c.begin()), "deque::erase(first, last) called with an invalid range");
-
   return 0;
 }
diff --git a/libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter.pass.cpp
new file mode 100644
index 0000000000000..240715df50b24
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+//
+// iterator erase(const_iterator position);
+
+// Make sure we check that the iterator is within the container.
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <vector>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  // With an invalid iterator
+  {
+    std::vector<int> v = {1, 2, 3, 4, 5};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v.erase(v.end()),
+        "vector::erase(iterator) called with an iterator that isn't a valid iterator into this vector");
+  }
+
+  // With an iterator from another container
+  {
+    std::vector<int> v1 = {1, 2, 3, 4, 5};
+    std::vector<int> v2 = {6, 7, 8, 9, 10};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v1.erase(v2.begin()),
+        "vector::erase(iterator) called with an iterator that isn't a valid iterator into this vector");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter_iter.pass.cpp b/libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter_iter.pass.cpp
new file mode 100644
index 0000000000000..a35db2588d692
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/vector/assert.erase.iter_iter.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+//
+// iterator erase(const_iterator first, const_iterator last);
+
+// Make sure we check that the iterator range is valid and within the container.
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <vector>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  // With first iterator from another container
+  {
+    std::vector<int> v1 = {1, 2, 3, 4, 5};
+    std::vector<int> v2 = {6, 7, 8, 9, 10};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v1.erase(v2.begin(), v1.end()),
+        "vector::erase(first, last) called with an iterator range that doesn't belong to this vector");
+  }
+
+  // With last iterator from another container
+  {
+    std::vector<int> v1 = {1, 2, 3, 4, 5};
+    std::vector<int> v2 = {6, 7, 8, 9, 10};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v1.erase(v1.begin(), v2.end()),
+        "vector::erase(first, last) called with a last iterator that doesn't fall within the vector");
+  }
+
+  // With both iterators from another container
+  {
+    std::vector<int> v1 = {1, 2, 3, 4, 5};
+    std::vector<int> v2 = {6, 7, 8, 9, 10};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v1.erase(v2.begin(), v2.end()),
+        "vector::erase(first, last) called with an iterator range that doesn't belong to this vector");
+  }
+
+  // With an invalid range
+  {
+    std::vector<int> v = {1, 2, 3, 4, 5};
+    TEST_LIBCPP_ASSERT_FAILURE(
+        v.erase(v.begin() + 2, v.begin()), "vector::erase(first, last) called with invalid range");
+  }
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter.pass.cpp
new file mode 100644
index 0000000000000..03746eab35137
--- /dev/null
+++ b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter.pass.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 we assert when erase(position) is called with an iterator that isn't a
+// valid iterator into the current string.
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+#include <string>
+
+#include "check_assertion.h"
+#include "min_allocator.h"
+
+template <class String>
+void test() {
+  {
+    String s("123");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        s.erase(s.end()),
+        "string::erase(iterator) called with an iterator that isn't a valid iterator into this string");
+  }
+
+  {
+    String s1("123");
+    String s2("456");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        s1.erase(s2.begin()),
+        "string::erase(iterator) called with an iterator that isn't a valid iterator into this string");
+  }
+}
+
+int main(int, char**) {
+  test<std::string>();
+  test<std::basic_string<char, std::char_traits<char>, min_allocator<char> > >();
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter_iter.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter_iter.pass.cpp
similarity index 50%
rename from libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter_iter.pass.cpp
rename to libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter_iter.pass.cpp
index ed97e21d9411e..4dc73dd33b849 100644
--- a/libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter_iter.pass.cpp
+++ b/libcxx/test/libcxx/strings/basic.string/string.modifiers/assert.erase.iter_iter.pass.cpp
@@ -8,48 +8,52 @@
 
 // <string>
 
-// Call erase(const_iterator first, const_iterator last); with invalid iterators
+// Make sure we assert when erase(first, last) is called with iterators that aren't valid.
 
 // REQUIRES: has-unix-headers
-// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03
+// UNSUPPORTED: c++03
+// UNSUPPORTED: libcpp-hardening-mode=none
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
 
 #include <string>
 
 #include "check_assertion.h"
 #include "min_allocator.h"
 
-template <class S>
+template <class String>
 void test() {
   // With first iterator from another container
   {
-    S l1("123");
-    S l2("123");
+    String s1("123");
+    String s2("123");
     TEST_LIBCPP_ASSERT_FAILURE(
-        l1.erase(l2.cbegin(), l1.cbegin() + 1),
-        "string::erase(iterator,  iterator) called with an iterator not referring to this string");
+        s1.erase(s2.cbegin(), s1.cbegin() + 1),
+        "string::erase(first, last) called with an iterator range that doesn't belong to this string");
   }
 
   // With second iterator from another container
   {
-    S l1("123");
-    S l2("123");
-    TEST_LIBCPP_ASSERT_FAILURE(l1.erase(l1.cbegin(), l2.cbegin() + 1), "Attempted to compare incomparable iterators");
+    String s1("123");
+    String s2("123");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        s1.erase(s1.cbegin(), s2.cbegin() + 1),
+        "string::erase(first, last) called with a last iterator that doesn't fall within the string");
   }
 
   // With both iterators from another container
   {
-    S l1("123");
-    S l2("123");
+    String s1("123");
+    String s2("123");
     TEST_LIBCPP_ASSERT_FAILURE(
-        l1.erase(l2.cbegin(), l2.cbegin() + 1),
-        "string::erase(iterator,  iterator) called with an iterator not referring to this string");
+        s1.erase(s2.cbegin(), s2.cbegin() + 1),
+        "string::erase(first, last) called with an iterator range that doesn't belong to this string");
   }
 
   // With an invalid range
   {
-    S l1("123");
+    String s1("123");
     TEST_LIBCPP_ASSERT_FAILURE(
-        l1.erase(l1.cbegin() + 1, l1.cbegin()), "string::erase(first, last) called with invalid range");
+        s1.erase(s1.cbegin() + 1, s1.cbegin()), "string::erase(first, last) called with invalid range");
   }
 }
 
diff --git a/libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter.pass.cpp b/libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter.pass.cpp
deleted file mode 100644
index 93fd6caf0ba23..0000000000000
--- a/libcxx/test/libcxx/strings/basic.string/string.modifiers/debug.erase.iter.pass.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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>
-
-// Call erase(const_iterator position) with an iterator from another container
-
-// REQUIRES: has-unix-headers
-// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03
-
-#include <string>
-
-#include "check_assertion.h"
-#include "min_allocator.h"
-
-template <class S>
-void test() {
-  S l1("123");
-  S l2("123");
-  typename S::const_iterator i = l2.begin();
-  TEST_LIBCPP_ASSERT_FAILURE(
-      l1.erase(i), "string::erase(iterator) called with an iterator not referring to this string");
-}
-
-int main(int, char**) {
-  test<std::string>();
-  test<std::basic_string<char, std::char_traits<char>, min_allocator<char> > >();
-
-  return 0;
-}

>From 591e84eb213fcb482c8638e4b0f8ff4d42e7efa6 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 9 Jul 2024 15:29:57 -0400
Subject: [PATCH 4/4] Formatting

---
 .../containers/sequences/deque/assert.erase.iter.pass.cpp      | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp
index 5190bee4998b7..30b07888e0812 100644
--- a/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.erase.iter.pass.cpp
@@ -23,8 +23,7 @@ int main(int, char**) {
   // With an invalid iterator
   {
     std::deque<int> v = {1, 2, 3, 4, 5};
-    TEST_LIBCPP_ASSERT_FAILURE(
-        v.erase(v.end()), "deque::erase(iterator) called with a non-dereferenceable iterator");
+    TEST_LIBCPP_ASSERT_FAILURE(v.erase(v.end()), "deque::erase(iterator) called with a non-dereferenceable iterator");
   }
 
   // Note that we currently can't catch misuse by erasing with an iterator from another container.



More information about the libcxx-commits mailing list