[libcxx-commits] [libcxx] [libc++][hardening] Add hardening assertions to std::deque (PR #79397)

David Benjamin via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jan 24 18:33:31 PST 2024


https://github.com/davidben created https://github.com/llvm/llvm-project/pull/79397

This aligns std::deque with std::vector w.r.t. hardening checks. There's probably more that can be done with iterators, but start with this.

This caught a bug with one of libc++'s tests. One of the erase calls in asan_caterpillar.pass.cpp was a no-op because the iterators were in the other order. (deque::erase happened to cleanly do nothing when the distance is negative.)

Fixes #63809

>From 55711abfc7bb1fdf1e1ad83fda43874500a20e96 Mon Sep 17 00:00:00 2001
From: David Benjamin <davidben at google.com>
Date: Wed, 24 Jan 2024 21:30:27 -0500
Subject: [PATCH] [libc++][hardening] Add hardening assertions to std::deque

This aligns std::deque with std::vector w.r.t. hardening checks. There's
probably more that can be done with iterators, but start with this.

This caught a bug with one of libc++'s tests. One of the erase calls in
asan_caterpillar.pass.cpp was a no-op because the iterators were in the
other order. (deque::erase happened to cleanly do nothing when the
distance is negative.)

Fixes #63809
---
 libcxx/include/deque                          | 10 ++++
 .../sequences/deque/asan_caterpillar.pass.cpp |  2 +-
 .../sequences/deque/assert.pass.cpp           | 54 +++++++++++++++++++
 3 files changed, 65 insertions(+), 1 deletion(-)
 create mode 100644 libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp

diff --git a/libcxx/include/deque b/libcxx/include/deque
index fca8b3d6e2c737f..68a8dd51cc89255 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -1487,6 +1487,7 @@ void deque<_Tp, _Allocator>::shrink_to_fit() _NOEXCEPT {
 
 template <class _Tp, class _Allocator>
 inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::operator[](size_type __i) _NOEXCEPT {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__i < size(), "deque::operator[] index out of bounds");
   size_type __p = __start_ + __i;
   return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
 }
@@ -1494,6 +1495,7 @@ inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::operat
 template <class _Tp, class _Allocator>
 inline typename deque<_Tp, _Allocator>::const_reference
 deque<_Tp, _Allocator>::operator[](size_type __i) const _NOEXCEPT {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__i < size(), "deque::operator[] index out of bounds");
   size_type __p = __start_ + __i;
   return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
 }
@@ -1516,22 +1518,26 @@ inline typename deque<_Tp, _Allocator>::const_reference deque<_Tp, _Allocator>::
 
 template <class _Tp, class _Allocator>
 inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::front() _NOEXCEPT {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::front called on an empty deque");
   return *(*(__map_.begin() + __start_ / __block_size) + __start_ % __block_size);
 }
 
 template <class _Tp, class _Allocator>
 inline typename deque<_Tp, _Allocator>::const_reference deque<_Tp, _Allocator>::front() const _NOEXCEPT {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::front called on an empty deque");
   return *(*(__map_.begin() + __start_ / __block_size) + __start_ % __block_size);
 }
 
 template <class _Tp, class _Allocator>
 inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::back() _NOEXCEPT {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::back called on an empty deque");
   size_type __p = size() + __start_ - 1;
   return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
 }
 
 template <class _Tp, class _Allocator>
 inline typename deque<_Tp, _Allocator>::const_reference deque<_Tp, _Allocator>::back() const _NOEXCEPT {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::back called on an empty deque");
   size_type __p = size() + __start_ - 1;
   return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
 }
@@ -2255,6 +2261,7 @@ void deque<_Tp, _Allocator>::__add_back_capacity(size_type __n) {
 
 template <class _Tp, class _Allocator>
 void deque<_Tp, _Allocator>::pop_front() {
+  _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::pop_front called on an empty deque");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
   allocator_type& __a   = __alloc();
@@ -2395,6 +2402,8 @@ 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");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
   iterator __b          = begin();
@@ -2420,6 +2429,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 <= __l, "deque::erase(first, last) called with an invalid range");
   size_type __old_sz    = size();
   size_type __old_start = __start_;
   difference_type __n   = __l - __f;
diff --git a/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp
index d3c6e0493ee28fd..4c7b2317d04d3a8 100644
--- a/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp
+++ b/libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp
@@ -25,7 +25,7 @@ void test1() {
 
   for (int i = 0; i < 1100; i += 1) {
     test.insert(test.begin(), buff, buff + 320);
-    test.erase(test.end(), test.end() - 320);
+    test.erase(test.end() - 320, test.end());
   }
 
   test.insert(test.begin(), buff, buff + 32000);
diff --git a/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp
new file mode 100644
index 000000000000000..a0c80ce30ec78d9
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/deque/assert.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// 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>
+
+// Test hardening assertions for std::deque.
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: libcpp-hardening-mode=none
+
+#include <deque>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  std::deque<int> c;
+  TEST_LIBCPP_ASSERT_FAILURE(c.front(), "deque::front called on an empty deque");
+  TEST_LIBCPP_ASSERT_FAILURE(c.back(), "deque::back called on an empty deque");
+  TEST_LIBCPP_ASSERT_FAILURE(c[0], "deque::operator[] index out of bounds");
+  TEST_LIBCPP_ASSERT_FAILURE(c.pop_front(), "deque::pop_front called on an empty deque");
+  TEST_LIBCPP_ASSERT_FAILURE(c.pop_back(), "deque::pop_back called on an empty deque");
+
+  // Repeat the test with a const reference to test the const overloads.
+  {
+    const std::deque<int>& cc = c;
+    TEST_LIBCPP_ASSERT_FAILURE(cc.front(), "deque::front called on an empty deque");
+    TEST_LIBCPP_ASSERT_FAILURE(cc.back(), "deque::back called on an empty deque");
+    TEST_LIBCPP_ASSERT_FAILURE(cc[0], "deque::operator[] index out of bounds");
+  }
+
+  c.push_back(1);
+  c.push_back(2);
+  c.push_back(3);
+  TEST_LIBCPP_ASSERT_FAILURE(c[3], "deque::operator[] index out of bounds");
+  TEST_LIBCPP_ASSERT_FAILURE(c[100], "deque::operator[] index out of bounds");
+
+  // Repeat the test with a const reference to test the const overloads.
+  {
+    const std::deque<int>& cc = c;
+    TEST_LIBCPP_ASSERT_FAILURE(cc[3], "deque::operator[] index out of bounds");
+    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;
+}



More information about the libcxx-commits mailing list