[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