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

via libcxx-commits libcxx-commits at lists.llvm.org
Thu Sep 10 16:53:04 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

<details>
<summary>Changes</summary>

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

---
Full diff: https://github.com/llvm/llvm-project/pull/222813.diff


4 Files Affected:

- (modified) libcxx/include/__algorithm/find.h (+6-1) 
- (modified) libcxx/include/__concepts/swappable.h (+3-2) 
- (modified) libcxx/include/__memory/valid_range.h (+11-5) 
- (added) libcxx/test/libcxx/algorithms/robust_against_volatile_pointers.compile.pass.cpp (+238) 


``````````diff
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
+}

``````````

</details>


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


More information about the libcxx-commits mailing list