[libcxx-commits] [libcxx] [libc++] Assert that the elements selected by a valarray subscript exist (PR #236120)

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Oct 27 06:56:21 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Ilyas Mallah (ilyas-mallah)

<details>
<summary>Changes</summary>

[valarray.sub] requires the elements selected by a `slice`, `gslice`, `valarray<bool>` or `valarray<size_t>` subscript to exist, but nothing checks it. The non-const overloads return a proxy that writes through a raw pointer, so `v[std::slice(4, 4, 2)] = 1` on an 8-element `v` writes two elements past the buffer. ASan reports a heap-buffer-overflow on `main` for all four kinds. The checks on `operator[](size_t)` from #<!-- -->120685 and on the proxies' `__get()` from #<!-- -->76528 don't see these accesses.

This adds a `_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS` to the seven non-const overloads. The `slice` check is O(1) and can't wrap around. The `gslice` and index checks loop over the positions, which the proxy then walks anyway. The const overloads already trap.

The test also passes with the observe semantic, since nothing is accessed after a failed check. The size checks for the valarray operators are in #<!-- -->230606, which doesn't overlap.

AI usage: Claude Code helped with the test runs and test scaffolding. I traced the issue, made the fix, and validated every change.


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


2 Files Affected:

- (modified) libcxx/include/valarray (+42) 
- (added) libcxx/test/libcxx/numerics/numarray/assert.subscript.pass.cpp (+115) 


``````````diff
diff --git a/libcxx/include/valarray b/libcxx/include/valarray
index df3ce3252e3d2c..ae6466dc80e37f 100644
--- a/libcxx/include/valarray
+++ b/libcxx/include/valarray
@@ -1047,6 +1047,34 @@ struct _BinaryOp<_Op, valarray<_Tp>, valarray<_Tp> > {
   _LIBCPP_HIDE_FROM_ABI size_t size() const { return __a0_.size(); }
 };
 
+// Bounds checks for the subscripts that return a slice_array, gslice_array, mask_array or indirect_array.
+
+// Whether start() + i * stride() is below __n for every i < size(), computed without overflow.
+inline _LIBCPP_HIDE_FROM_ABI bool __slice_in_bounds(const slice& __sl, size_t __n) {
+  if (__sl.size() == 0)
+    return true;
+  if (__sl.start() >= __n)
+    return false;
+  return __sl.stride() == 0 || __sl.size() - 1 <= (__n - 1 - __sl.start()) / __sl.stride();
+}
+
+inline _LIBCPP_HIDE_FROM_ABI bool __indices_in_bounds(const size_t* __first, const size_t* __last, size_t __n) {
+  for (; __first != __last; ++__first)
+    if (*__first >= __n)
+      return false;
+  return true;
+}
+
+// Whether the mask [__first, __last) selects no position at or past __n.
+inline _LIBCPP_HIDE_FROM_ABI bool __mask_in_bounds(const bool* __first, const bool* __last, size_t __n) {
+  if (static_cast<size_t>(__last - __first) <= __n)
+    return true;
+  for (__first += __n; __first != __last; ++__first)
+    if (*__first)
+      return false;
+  return true;
+}
+
 // slice_array
 
 template <class _Tp>
@@ -2224,6 +2252,8 @@ inline __val_expr<__slice_expr<const valarray<_Tp>&> > valarray<_Tp>::operator[]
 
 template <class _Tp>
 inline slice_array<_Tp> valarray<_Tp>::operator[](slice __s) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
+      std::__slice_in_bounds(__s, size()), "valarray::operator[](slice) selects elements out of bounds");
   return slice_array<value_type>(__s, *this);
 }
 
@@ -2234,6 +2264,8 @@ inline __val_expr<__indirect_expr<const valarray<_Tp>&> > valarray<_Tp>::operato
 
 template <class _Tp>
 inline gslice_array<_Tp> valarray<_Tp>::operator[](const gslice& __gs) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(std::__indices_in_bounds(__gs.__1d_.__begin_, __gs.__1d_.__end_, size()),
+                                      "valarray::operator[](gslice) selects elements out of bounds");
   return gslice_array<value_type>(__gs, *this);
 }
 
@@ -2246,6 +2278,8 @@ inline __val_expr<__indirect_expr<const valarray<_Tp>&> > valarray<_Tp>::operato
 
 template <class _Tp>
 inline gslice_array<_Tp> valarray<_Tp>::operator[](gslice&& __gs) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(std::__indices_in_bounds(__gs.__1d_.__begin_, __gs.__1d_.__end_, size()),
+                                      "valarray::operator[](gslice) selects elements out of bounds");
   return gslice_array<value_type>(std::move(__gs), *this);
 }
 
@@ -2258,6 +2292,8 @@ inline __val_expr<__mask_expr<const valarray<_Tp>&> > valarray<_Tp>::operator[](
 
 template <class _Tp>
 inline mask_array<_Tp> valarray<_Tp>::operator[](const valarray<bool>& __vb) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(std::__mask_in_bounds(__vb.__begin_, __vb.__end_, size()),
+                                      "valarray::operator[](valarray<bool>) selects elements out of bounds");
   return mask_array<value_type>(__vb, *this);
 }
 
@@ -2270,6 +2306,8 @@ inline __val_expr<__mask_expr<const valarray<_Tp>&> > valarray<_Tp>::operator[](
 
 template <class _Tp>
 inline mask_array<_Tp> valarray<_Tp>::operator[](valarray<bool>&& __vb) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(std::__mask_in_bounds(__vb.__begin_, __vb.__end_, size()),
+                                      "valarray::operator[](valarray<bool>) selects elements out of bounds");
   return mask_array<value_type>(std::move(__vb), *this);
 }
 
@@ -2283,6 +2321,8 @@ valarray<_Tp>::operator[](const valarray<size_t>& __vs) const {
 
 template <class _Tp>
 inline indirect_array<_Tp> valarray<_Tp>::operator[](const valarray<size_t>& __vs) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(std::__indices_in_bounds(__vs.__begin_, __vs.__end_, size()),
+                                      "valarray::operator[](valarray<size_t>) selects elements out of bounds");
   return indirect_array<value_type>(__vs, *this);
 }
 
@@ -2295,6 +2335,8 @@ inline __val_expr<__indirect_expr<const valarray<_Tp>&> > valarray<_Tp>::operato
 
 template <class _Tp>
 inline indirect_array<_Tp> valarray<_Tp>::operator[](valarray<size_t>&& __vs) {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(std::__indices_in_bounds(__vs.__begin_, __vs.__end_, size()),
+                                      "valarray::operator[](valarray<size_t>) selects elements out of bounds");
   return indirect_array<value_type>(std::move(__vs), *this);
 }
 
diff --git a/libcxx/test/libcxx/numerics/numarray/assert.subscript.pass.cpp b/libcxx/test/libcxx/numerics/numarray/assert.subscript.pass.cpp
new file mode 100644
index 00000000000000..38d20f0c655aeb
--- /dev/null
+++ b/libcxx/test/libcxx/numerics/numarray/assert.subscript.pass.cpp
@@ -0,0 +1,115 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: can-test-hardening-assertions-fast
+
+// <valarray>
+
+// template<class T> class valarray;
+
+// slice_array<value_type> operator[](slice s);
+// gslice_array<value_type> operator[](const gslice& gs);
+// gslice_array<value_type> operator[](gslice&& gs);
+// mask_array<value_type> operator[](const valarray<bool>& vb);
+// mask_array<value_type> operator[](valarray<bool>&& vb);
+// indirect_array<value_type> operator[](const valarray<size_t>& vs);
+// indirect_array<value_type> operator[](valarray<size_t>&& vs);
+
+// Test that the selected elements must exist.
+
+#include <cstddef>
+#include <limits>
+#include <valarray>
+
+#include "check_assertion.h"
+
+std::gslice make_gslice(std::size_t start, std::size_t len0, std::size_t len1, std::size_t str0, std::size_t str1) {
+  std::size_t lengths[] = {len0, len1};
+  std::size_t strides[] = {str0, str1};
+  return std::gslice(start, std::valarray<std::size_t>(lengths, 2), std::valarray<std::size_t>(strides, 2));
+}
+
+int main(int, char**) {
+  std::valarray<int> array(10);
+
+  { // slice
+    // Check that there's no assertion on valid slices.
+    (void)array[std::slice(0, 10, 1)];
+    (void)array[std::slice(1, 5, 2)];
+    (void)array[std::slice(9, 3, 0)];
+    (void)array[std::slice(20, 0, 1)];
+
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[std::slice(0, 11, 1)], "valarray::operator[](slice) selects elements out of bounds");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[std::slice(10, 1, 1)], "valarray::operator[](slice) selects elements out of bounds");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[std::slice(4, 4, 2)], "valarray::operator[](slice) selects elements out of bounds");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[std::slice(10, 3, 0)], "valarray::operator[](slice) selects elements out of bounds");
+
+    // (size() - 1) * stride() wraps around to 0, but the second element is far out of bounds.
+    const std::size_t half = std::size_t(1) << (std::numeric_limits<std::size_t>::digits - 1);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[std::slice(0, 3, half)], "valarray::operator[](slice) selects elements out of bounds");
+  }
+
+  { // gslice
+    // Check that there's no assertion on valid generalized slices.
+    const std::gslice all = make_gslice(0, 2, 5, 5, 1); // 0 to 9
+    (void)array[all];
+    (void)array[make_gslice(3, 2, 2, 5, 1)];  // 3, 4, 8, 9
+    (void)array[make_gslice(9, 3, 2, 0, 0)];  // 9 six times
+    (void)array[make_gslice(20, 0, 2, 1, 1)]; // empty
+
+    const std::gslice past_end = make_gslice(6, 2, 2, 4, 1); // 6, 7, 10, 11
+    TEST_LIBCPP_ASSERT_FAILURE(array[past_end], "valarray::operator[](gslice) selects elements out of bounds");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[make_gslice(1, 2, 5, 5, 1)], "valarray::operator[](gslice) selects elements out of bounds"); // 1 to 10
+  }
+
+  { // valarray<bool>
+    // Check that there's no assertion on valid masks, including masks of a different size that select no element
+    // past the end.
+    const std::valarray<bool> all(true, 10);
+    (void)array[all];
+    (void)array[std::valarray<bool>(true, 4)];
+    (void)array[std::valarray<bool>(false, 20)];
+    std::valarray<bool> longer(false, 12);
+    longer[9] = true;
+    (void)array[longer];
+
+    std::valarray<bool> one_past(false, 11);
+    one_past[10] = true;
+    TEST_LIBCPP_ASSERT_FAILURE(array[one_past], "valarray::operator[](valarray<bool>) selects elements out of bounds");
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[std::valarray<bool>(true, 12)], "valarray::operator[](valarray<bool>) selects elements out of bounds");
+
+    std::valarray<int> empty;
+    TEST_LIBCPP_ASSERT_FAILURE(
+        empty[std::valarray<bool>(true, 1)], "valarray::operator[](valarray<bool>) selects elements out of bounds");
+  }
+
+  { // valarray<size_t>
+    // Check that there's no assertion on valid indices.
+    std::size_t valid[] = {0, 9, 9, 3};
+    const std::valarray<std::size_t> indices(valid, 4);
+    (void)array[indices];
+    (void)array[std::valarray<std::size_t>()];
+
+    std::size_t past_end[] = {0, 9, 10, 3};
+    const std::valarray<std::size_t> past_end_indices(past_end, 4);
+    TEST_LIBCPP_ASSERT_FAILURE(
+        array[past_end_indices], "valarray::operator[](valarray<size_t>) selects elements out of bounds");
+    std::size_t max[] = {static_cast<std::size_t>(-1)};
+    TEST_LIBCPP_ASSERT_FAILURE(array[std::valarray<std::size_t>(max, 1)],
+                               "valarray::operator[](valarray<size_t>) selects elements out of bounds");
+  }
+
+  return 0;
+}

``````````

</details>


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


More information about the libcxx-commits mailing list