[libcxx-commits] [libcxx] [libc++] Harden the algorithms against volatile pointers (PR #222813)

Louis Dionne via libcxx-commits libcxx-commits at lists.llvm.org
Tue Sep 15 13:03:40 PDT 2026


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

>From 663f6dabe9b2f700f25f3c4189e0a78892e3f4e3 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Thu, 10 Sep 2026 16:30:40 -0700
Subject: [PATCH 1/4] [libc++] Harden the algorithms against volatile pointers

A pointer to volatile is a valid Cpp17InputIterator, so the algorithms
that only require an input iterator are required to work with one. We
also support the algorithms requiring stronger iterators on a best-effort
basis, so add a test for those.

This property was partially broken by #207274 and some prior commits,
so these are fixed as part of this patch.

Supersedes #213719

Assisted by Claude
---
 libcxx/include/__algorithm/find.h             |   7 +-
 libcxx/include/__concepts/swappable.h         |   5 +-
 libcxx/include/__memory/valid_range.h         |  16 +-
 ...against_volatile_pointers.compile.pass.cpp | 238 ++++++++++++++++++
 4 files changed, 258 insertions(+), 8 deletions(-)
 create mode 100644 libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp

diff --git a/libcxx/include/__algorithm/find.h b/libcxx/include/__algorithm/find.h
index be45c8bfa966c..d941a157a3d25 100644
--- a/libcxx/include/__algorithm/find.h
+++ b/libcxx/include/__algorithm/find.h
@@ -29,6 +29,7 @@
 #include <__type_traits/is_equality_comparable.h>
 #include <__type_traits/is_integral.h>
 #include <__type_traits/is_signed.h>
+#include <__type_traits/is_volatile.h>
 #include <__utility/move.h>
 #include <limits>
 
@@ -116,10 +117,14 @@ _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp* __find_vectorized(_Tp* __first, _Tp* __last,
 
 #ifndef _LIBCPP_CXX03_LANG
 // trivially equality comparable implementations
+// Note that we can't use any of the optimized implementations below for volatile types, since they read the range
+// through a non-volatile lvalue.
 template <class _Tp,
           class _Up,
           class _Proj,
-          __enable_if_t<__is_identity<_Proj>::value && __is_trivially_equality_comparable_v<_Tp, _Up>, int> = 0>
+          __enable_if_t<__is_identity<_Proj>::value && !is_volatile<_Tp>::value && !is_volatile<_Up>::value &&
+                            __is_trivially_equality_comparable_v<_Tp, _Up>,
+                        int> = 0>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 _Tp* __find(_Tp* __first, _Tp* __last, const _Up& __value, _Proj&) {
   if constexpr (sizeof(_Tp) == 1) {
     if (auto __ret = std::__constexpr_memchr(__first, __value, __last - __first))
diff --git a/libcxx/include/__concepts/swappable.h b/libcxx/include/__concepts/swappable.h
index 5077772781619..87c515e768c7e 100644
--- a/libcxx/include/__concepts/swappable.h
+++ b/libcxx/include/__concepts/swappable.h
@@ -19,7 +19,6 @@
 #include <__type_traits/is_nothrow_assignable.h>
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__type_traits/remove_cvref.h>
-#include <__utility/exchange.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
 
@@ -92,7 +91,9 @@ struct __fn {
   template <__exchangeable _Tp>
   _LIBCPP_HIDE_FROM_ABI constexpr void operator()(_Tp& __x, _Tp& __y) const
       noexcept(is_nothrow_move_constructible_v<_Tp> && is_nothrow_move_assignable_v<_Tp>) {
-    __y = std::exchange(__x, std::move(__y));
+    _Tp __tmp(std::move(__x));
+    __x = std::move(__y);
+    __y = std::move(__tmp);
   }
 };
 } // namespace __swap
diff --git a/libcxx/include/__memory/valid_range.h b/libcxx/include/__memory/valid_range.h
index 32c98588fe39a..7d987c2d71f01 100644
--- a/libcxx/include/__memory/valid_range.h
+++ b/libcxx/include/__memory/valid_range.h
@@ -17,7 +17,9 @@
 #include <__memory/pointer_traits.h>
 #include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_same.h>
+#include <__type_traits/is_volatile.h>
 #include <__type_traits/remove_cvref.h>
+#include <__type_traits/remove_pointer.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -59,11 +61,15 @@ __assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]]
                 is_same<__remove_cvref_t<_Iter>, __remove_cvref_t<_Sent>>::value) {
     _LIBCPP_ASSERT_INTERNAL(std::__is_valid_range(std::__to_address(__first), std::__to_address(__last)),
                             "Valid range assumption does not hold");
-    if (!__libcpp_is_constant_evaluated()) {
-      using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
-      __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type));
-      (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first));
-      (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last));
+    // We can't make these assumptions for volatile pointers since the builtins take a plain pointer.
+    using __pointer_type = decltype(std::__to_address(__first));
+    if constexpr (!is_volatile<__remove_pointer_t<__pointer_type> >::value) {
+      if (!__libcpp_is_constant_evaluated()) {
+        using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
+        __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type));
+        (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first));
+        (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last));
+      }
     }
   }
 #endif
diff --git a/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp b/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp
new file mode 100644
index 0000000000000..5e4bb94eebc32
--- /dev/null
+++ b/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp
@@ -0,0 +1,238 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Ensure that the algorithms can be instantiated with volatile pointers.
+//
+// A pointer to volatile is a valid Cpp17InputIterator, so algorithms that only require an input iterator are
+// required by the Standard to work with one. It is however not a valid Cpp17ForwardIterator, since its
+// reference type (volatile T&) is neither value_type& nor const value_type&. Supporting the algorithms that
+// require a forward iterator or better is an extension, which we provide on a best-effort basis.
+
+// UNSUPPORTED: c++03
+
+#include <algorithm>
+#include <functional>
+#include <numeric>
+
+#include "test_macros.h"
+
+#if TEST_STD_VER >= 20
+#  include <ranges>
+#  include <span>
+#endif
+
+// Named functions instead of lambdas, to keep the diagnostics readable.
+inline bool is_positive(int x) { return x > 0; }
+inline int identity(int x) { return x; }
+inline int plus(int x, int y) { return x + y; }
+inline int zero() { return 0; }
+inline void observe(int) {}
+
+using It = int volatile*;
+
+void test(It first,
+          It mid,
+          It last,
+          It first2,
+          It last2,
+          int* pfirst,
+          int* plast,
+          int value,
+          int volatile& volatile_value,
+          char volatile* cfirst,
+          char volatile* clast,
+          char* pcfirst,
+          char* pclast,
+          char cvalue) {
+  // Reading from a volatile range.
+  (void)std::find(first, last, value);
+  (void)std::find(first, last, volatile_value);
+  (void)std::find(pfirst, plast, volatile_value); // plain range, volatile value
+  (void)std::find_if(first, last, is_positive);
+  (void)std::find_if_not(first, last, is_positive);
+  (void)std::count(first, last, value);
+  (void)std::count_if(first, last, is_positive);
+  (void)std::all_of(first, last, is_positive);
+  (void)std::any_of(first, last, is_positive);
+  (void)std::none_of(first, last, is_positive);
+  (void)std::for_each(first, last, observe);
+  (void)std::adjacent_find(first, last);
+  (void)std::search(first, last, first2, last2);
+  (void)std::search_n(first, last, 2, value);
+  (void)std::find_end(first, last, first2, last2);
+  (void)std::find_first_of(first, last, first2, last2);
+  (void)std::min_element(first, last);
+  (void)std::max_element(first, last);
+  (void)std::minmax_element(first, last);
+  (void)std::is_sorted(first, last);
+  (void)std::is_sorted_until(first, last);
+  (void)std::is_partitioned(first, last, is_positive);
+  (void)std::partition_point(first, last, is_positive);
+  (void)std::lower_bound(first, last, value);
+  (void)std::upper_bound(first, last, value);
+  (void)std::equal_range(first, last, value);
+  (void)std::binary_search(first, last, value);
+  (void)std::is_heap(first, last);
+  (void)std::is_heap_until(first, last);
+  (void)std::equal(first, last, first2);                          // volatile against volatile
+  (void)std::equal(first, last, pfirst);                          // volatile against plain
+  (void)std::mismatch(first, last, pfirst);                       //
+  (void)std::lexicographical_compare(first, last, first2, last2); //
+  (void)std::lexicographical_compare(first, last, pfirst, plast); //
+  (void)std::is_permutation(first, last, first2);
+  (void)std::includes(first, last, first2, last2);
+#if TEST_STD_VER >= 14
+  (void)std::equal(first, last, first2, last2);
+  (void)std::equal(first, last, pfirst, plast);
+  (void)std::mismatch(first, last, pfirst, plast);
+#endif
+
+  // Writing into a volatile range.
+  (void)std::copy(pfirst, plast, first);
+  (void)std::copy_n(pfirst, 3, first);
+  (void)std::copy_backward(pfirst, plast, last);
+  (void)std::copy_if(pfirst, plast, first, is_positive);
+  (void)std::move(pfirst, plast, first);
+  (void)std::move_backward(pfirst, plast, last);
+  (void)std::fill(first, last, value);
+  (void)std::fill_n(first, 3, value);
+  (void)std::generate(first, last, zero);
+  (void)std::generate_n(first, 3, zero);
+  (void)std::transform(pfirst, plast, first, identity);
+  (void)std::transform(pfirst, plast, pfirst, first, plus);
+  (void)std::replace_copy(pfirst, plast, first, value, value);
+  (void)std::replace_copy_if(pfirst, plast, first, is_positive, value);
+  (void)std::remove_copy(pfirst, plast, first, value);
+  (void)std::remove_copy_if(pfirst, plast, first, is_positive);
+  (void)std::unique_copy(pfirst, plast, first);
+  (void)std::reverse_copy(pfirst, plast, first);
+  (void)std::rotate_copy(pfirst, pfirst + 1, plast, first);
+  (void)std::merge(pfirst, plast, pfirst, plast, first);
+  (void)std::set_union(pfirst, plast, pfirst, plast, first);
+  (void)std::set_intersection(pfirst, plast, pfirst, plast, first);
+  (void)std::set_difference(pfirst, plast, pfirst, plast, first);
+  (void)std::set_symmetric_difference(pfirst, plast, pfirst, plast, first);
+  (void)std::partition_copy(pfirst, plast, first, first, is_positive);
+  (void)std::partial_sort_copy(pfirst, plast, first, last);
+
+  // Reading a volatile range into a non-volatile one.
+  (void)std::copy(first, last, pfirst);
+  (void)std::copy_backward(first, last, plast);
+  (void)std::copy_if(first, last, pfirst, is_positive);
+  (void)std::move(first, last, pfirst);
+  (void)std::transform(first, last, pfirst, identity);
+  (void)std::remove_copy(first, last, pfirst, value);
+  (void)std::unique_copy(first, last, pfirst);
+  (void)std::reverse_copy(first, last, pfirst);
+  (void)std::merge(first, last, first, last, pfirst);
+
+  // Modifying a volatile range in place.
+  (void)std::copy(first, last, first);
+  (void)std::transform(first, last, first, identity);
+  (void)std::replace(first, last, value, value);
+  (void)std::replace_if(first, last, is_positive, value);
+
+  // Permuting a volatile range in place.
+  (void)std::swap_ranges(first, mid, mid);
+  (void)std::iter_swap(first, mid);
+  (void)std::reverse(first, last);
+  (void)std::rotate(first, mid, last);
+  (void)std::remove(first, last, value);
+  (void)std::remove_if(first, last, is_positive);
+  (void)std::unique(first, last);
+  (void)std::partition(first, last, is_positive);
+  (void)std::stable_partition(first, last, is_positive);
+  (void)std::next_permutation(first, last);
+  (void)std::prev_permutation(first, last);
+
+  // Sorting and heaps.
+  (void)std::sort(first, last);
+  (void)std::stable_sort(first, last);
+  (void)std::partial_sort(first, mid, last);
+  (void)std::nth_element(first, mid, last);
+  (void)std::inplace_merge(first, mid, last);
+  (void)std::make_heap(first, last);
+  (void)std::push_heap(first, last);
+  (void)std::pop_heap(first, last);
+  (void)std::sort_heap(first, last);
+#if TEST_STD_VER >= 14
+  (void)std::sort(first, last, std::greater<>());
+#endif
+
+  // Exercise the memchr/memcmp based implementations.
+  (void)std::find(cfirst, clast, cvalue);
+  (void)std::count(cfirst, clast, cvalue);
+  (void)std::equal(cfirst, clast, pcfirst);
+  (void)std::mismatch(cfirst, clast, pcfirst);
+  (void)std::lexicographical_compare(cfirst, clast, pcfirst, pclast);
+  (void)std::copy(pcfirst, pclast, cfirst);
+  (void)std::copy(cfirst, clast, pcfirst);
+  (void)std::fill(cfirst, clast, cvalue);
+
+  // <numeric>
+  (void)std::accumulate(first, last, 0, plus);
+  (void)std::inner_product(first, last, first, 0);
+  (void)std::inner_product(first, last, pfirst, 0);
+  (void)std::iota(first, last, 0);
+  (void)std::partial_sum(first, last, pfirst);
+  (void)std::partial_sum(pfirst, plast, first);
+  (void)std::adjacent_difference(first, last, pfirst);
+  (void)std::adjacent_difference(pfirst, plast, first);
+#if TEST_STD_VER >= 17
+  (void)std::reduce(first, last, 0);
+  (void)std::transform_reduce(first, last, 0, plus, identity);
+  (void)std::transform_reduce(first, last, pfirst, 0);
+  (void)std::inclusive_scan(first, last, pfirst);
+  (void)std::inclusive_scan(pfirst, plast, first);
+  (void)std::exclusive_scan(first, last, pfirst, 0);
+  (void)std::exclusive_scan(pfirst, plast, first, 0);
+  (void)std::transform_inclusive_scan(first, last, pfirst, plus, identity);
+  (void)std::transform_exclusive_scan(first, last, pfirst, 0, plus, identity);
+#endif
+
+#if TEST_STD_VER >= 20
+  namespace ranges = std::ranges;
+
+  (void)ranges::find(first, last, value);
+  (void)ranges::find_if(first, last, is_positive);
+  (void)ranges::count(first, last, value);
+  (void)ranges::for_each(first, last, observe);
+  (void)ranges::equal(first, last, first, last);
+  (void)ranges::mismatch(first, last, pfirst, plast);
+  (void)ranges::min_element(first, last);
+  (void)ranges::lower_bound(first, last, value);
+  (void)ranges::fill(first, last, value);
+  (void)ranges::copy(pfirst, plast, first);
+  (void)ranges::copy(first, last, pfirst);
+  (void)ranges::transform(first, last, first, identity);
+  (void)ranges::remove(first, last, value);
+  (void)ranges::unique(first, last);
+  (void)ranges::make_heap(first, last);
+
+  (void)ranges::swap_ranges(first, mid, mid, last);
+  (void)ranges::reverse(first, last);
+  (void)ranges::rotate(first, mid, last);
+  (void)ranges::partition(first, last, is_positive);
+  (void)ranges::sort(first, last);
+  (void)ranges::stable_sort(first, last);
+  (void)ranges::nth_element(first, mid, last);
+
+  ranges::subrange range(first, last);
+  static_assert(ranges::contiguous_range<decltype(range)>);
+  (void)ranges::count(range, value);
+  (void)ranges::fill(range, value);
+  (void)ranges::for_each(range | std::views::take(2), observe);
+  (void)ranges::distance(range);
+  (void)ranges::sort(range);
+
+  std::span<int volatile> span(first, last);
+  static_assert(ranges::contiguous_range<decltype(span)>);
+  (void)ranges::count(span, value);
+  (void)ranges::sort(span);
+#endif
+}

>From bc510f37117c8437f3977271b52726c94b51ea79 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 15 Sep 2026 07:31:51 -0700
Subject: [PATCH 2/4] Fix CI

---
 .../iterator.cust/iterator.cust.swap/iter_swap.pass.cpp     | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap/iter_swap.pass.cpp b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap/iter_swap.pass.cpp
index 65a8cf281b8e0..8da165ea25ac4 100644
--- a/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap/iter_swap.pass.cpp
+++ b/libcxx/test/std/iterators/iterator.requirements/iterator.cust/iterator.cust.swap/iter_swap.pass.cpp
@@ -179,11 +179,7 @@ constexpr bool test()
   {
     auto arr = std::array<move_tracker, 2>();
     std::ranges::iter_swap(arr.begin(), arr.begin() + 1);
-    if (std::is_constant_evaluated()) {
-      assert(arr[0].moves() == 1 && arr[1].moves() == 3);
-    } else {
-      assert(arr[0].moves() == 1 && arr[1].moves() == 2);
-    }
+    assert(arr[0].moves() == 1 && arr[1].moves() == 2);
   }
   {
     int buff[2] = {1, 2};

>From 44d97bc318dfff4fa2749df5e14e534b619ecfc9 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 15 Sep 2026 07:46:14 -0700
Subject: [PATCH 3/4] const_cast

---
 libcxx/include/__memory/valid_range.h | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/libcxx/include/__memory/valid_range.h b/libcxx/include/__memory/valid_range.h
index 7d987c2d71f01..a81e19abbfcae 100644
--- a/libcxx/include/__memory/valid_range.h
+++ b/libcxx/include/__memory/valid_range.h
@@ -17,9 +17,7 @@
 #include <__memory/pointer_traits.h>
 #include <__type_traits/is_constant_evaluated.h>
 #include <__type_traits/is_same.h>
-#include <__type_traits/is_volatile.h>
 #include <__type_traits/remove_cvref.h>
-#include <__type_traits/remove_pointer.h>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -61,15 +59,14 @@ __assume_valid_range([[__maybe_unused__]] _Iter&& __first, [[__maybe_unused__]]
                 is_same<__remove_cvref_t<_Iter>, __remove_cvref_t<_Sent>>::value) {
     _LIBCPP_ASSERT_INTERNAL(std::__is_valid_range(std::__to_address(__first), std::__to_address(__last)),
                             "Valid range assumption does not hold");
-    // We can't make these assumptions for volatile pointers since the builtins take a plain pointer.
-    using __pointer_type = decltype(std::__to_address(__first));
-    if constexpr (!is_volatile<__remove_pointer_t<__pointer_type> >::value) {
-      if (!__libcpp_is_constant_evaluated()) {
-        using __value_type = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
-        __builtin_assume_dereferenceable(std::__to_address(__first), (__last - __first) * sizeof(__value_type));
-        (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__first));
-        (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(std::__to_address(__last));
-      }
+    // We use const_cast to strip volatile-ness because the builtins take plain pointers.
+    if (!__libcpp_is_constant_evaluated()) {
+      using __value_type        = typename iterator_traits<__remove_cvref_t<_Iter>>::value_type;
+      __value_type* __first_ptr = const_cast<__value_type*>(std::__to_address(__first));
+      __value_type* __last_ptr  = const_cast<__value_type*>(std::__to_address(__last));
+      __builtin_assume_dereferenceable(__first_ptr, (__last_ptr - __first_ptr) * sizeof(__value_type));
+      (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(__first_ptr);
+      (void)std::__assume_aligned<_LIBCPP_ALIGNOF(__value_type)>(__last_ptr);
     }
   }
 #endif

>From fbb66a1323876aa8b8793b7012304d7565ed3046 Mon Sep 17 00:00:00 2001
From: Louis Dionne <ldionne.2 at gmail.com>
Date: Tue, 15 Sep 2026 13:03:26 -0700
Subject: [PATCH 4/4] Reword comment

---
 .../robust_against_volatile_pointers.compile.pass.cpp       | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp b/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp
index 5e4bb94eebc32..77402854ddced 100644
--- a/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp
+++ b/libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp
@@ -8,10 +8,8 @@
 
 // Ensure that the algorithms can be instantiated with volatile pointers.
 //
-// A pointer to volatile is a valid Cpp17InputIterator, so algorithms that only require an input iterator are
-// required by the Standard to work with one. It is however not a valid Cpp17ForwardIterator, since its
-// reference type (volatile T&) is neither value_type& nor const value_type&. Supporting the algorithms that
-// require a forward iterator or better is an extension, which we provide on a best-effort basis.
+// The C++ Standard is not always clear about whether volatile pointers are supported in algorithms,
+// however libc++ supports that everywhere on a best-effort basis.
 
 // UNSUPPORTED: c++03
 



More information about the libcxx-commits mailing list