Skip to content

Commit 0f9ab7b

Browse files
authored
[libc++][hardening] Add hardening assertions to std::deque (#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
1 parent 976374d commit 0f9ab7b

File tree

3 files changed

+67
-1
lines changed

3 files changed

+67
-1
lines changed

libcxx/include/deque

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1487,13 +1487,15 @@ void deque<_Tp, _Allocator>::shrink_to_fit() _NOEXCEPT {
14871487

14881488
template <class _Tp, class _Allocator>
14891489
inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::operator[](size_type __i) _NOEXCEPT {
1490+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__i < size(), "deque::operator[] index out of bounds");
14901491
size_type __p = __start_ + __i;
14911492
return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
14921493
}
14931494

14941495
template <class _Tp, class _Allocator>
14951496
inline typename deque<_Tp, _Allocator>::const_reference
14961497
deque<_Tp, _Allocator>::operator[](size_type __i) const _NOEXCEPT {
1498+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(__i < size(), "deque::operator[] index out of bounds");
14971499
size_type __p = __start_ + __i;
14981500
return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
14991501
}
@@ -1516,22 +1518,26 @@ inline typename deque<_Tp, _Allocator>::const_reference deque<_Tp, _Allocator>::
15161518

15171519
template <class _Tp, class _Allocator>
15181520
inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::front() _NOEXCEPT {
1521+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::front called on an empty deque");
15191522
return *(*(__map_.begin() + __start_ / __block_size) + __start_ % __block_size);
15201523
}
15211524

15221525
template <class _Tp, class _Allocator>
15231526
inline typename deque<_Tp, _Allocator>::const_reference deque<_Tp, _Allocator>::front() const _NOEXCEPT {
1527+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::front called on an empty deque");
15241528
return *(*(__map_.begin() + __start_ / __block_size) + __start_ % __block_size);
15251529
}
15261530

15271531
template <class _Tp, class _Allocator>
15281532
inline typename deque<_Tp, _Allocator>::reference deque<_Tp, _Allocator>::back() _NOEXCEPT {
1533+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::back called on an empty deque");
15291534
size_type __p = size() + __start_ - 1;
15301535
return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
15311536
}
15321537

15331538
template <class _Tp, class _Allocator>
15341539
inline typename deque<_Tp, _Allocator>::const_reference deque<_Tp, _Allocator>::back() const _NOEXCEPT {
1540+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::back called on an empty deque");
15351541
size_type __p = size() + __start_ - 1;
15361542
return *(*(__map_.begin() + __p / __block_size) + __p % __block_size);
15371543
}
@@ -2255,6 +2261,7 @@ void deque<_Tp, _Allocator>::__add_back_capacity(size_type __n) {
22552261

22562262
template <class _Tp, class _Allocator>
22572263
void deque<_Tp, _Allocator>::pop_front() {
2264+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!empty(), "deque::pop_front called on an empty deque");
22582265
size_type __old_sz = size();
22592266
size_type __old_start = __start_;
22602267
allocator_type& __a = __alloc();
@@ -2395,6 +2402,8 @@ void deque<_Tp, _Allocator>::__move_construct_backward_and_check(
23952402

23962403
template <class _Tp, class _Allocator>
23972404
typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f) {
2405+
_LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(
2406+
__f != end(), "deque::erase(iterator) called with a non-dereferenceable iterator");
23982407
size_type __old_sz = size();
23992408
size_type __old_start = __start_;
24002409
iterator __b = begin();
@@ -2420,6 +2429,7 @@ typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_it
24202429

24212430
template <class _Tp, class _Allocator>
24222431
typename deque<_Tp, _Allocator>::iterator deque<_Tp, _Allocator>::erase(const_iterator __f, const_iterator __l) {
2432+
_LIBCPP_ASSERT_VALID_INPUT_RANGE(__f <= __l, "deque::erase(first, last) called with an invalid range");
24232433
size_type __old_sz = size();
24242434
size_type __old_start = __start_;
24252435
difference_type __n = __l - __f;

libcxx/test/libcxx/containers/sequences/deque/asan_caterpillar.pass.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ void test1() {
2525

2626
for (int i = 0; i < 1100; i += 1) {
2727
test.insert(test.begin(), buff, buff + 320);
28-
test.erase(test.end(), test.end() - 320);
28+
test.erase(test.end() - 320, test.end());
2929
}
3030

3131
test.insert(test.begin(), buff, buff + 32000);
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// <deque>
10+
11+
// Test hardening assertions for std::deque.
12+
13+
// REQUIRES: has-unix-headers
14+
// UNSUPPORTED: libcpp-hardening-mode=none
15+
// UNSUPPORTED: c++03
16+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
17+
18+
#include <deque>
19+
20+
#include "check_assertion.h"
21+
22+
int main(int, char**) {
23+
std::deque<int> c;
24+
TEST_LIBCPP_ASSERT_FAILURE(c.front(), "deque::front called on an empty deque");
25+
TEST_LIBCPP_ASSERT_FAILURE(c.back(), "deque::back called on an empty deque");
26+
TEST_LIBCPP_ASSERT_FAILURE(c[0], "deque::operator[] index out of bounds");
27+
TEST_LIBCPP_ASSERT_FAILURE(c.pop_front(), "deque::pop_front called on an empty deque");
28+
TEST_LIBCPP_ASSERT_FAILURE(c.pop_back(), "deque::pop_back called on an empty deque");
29+
30+
// Repeat the test with a const reference to test the const overloads.
31+
{
32+
const std::deque<int>& cc = c;
33+
TEST_LIBCPP_ASSERT_FAILURE(cc.front(), "deque::front called on an empty deque");
34+
TEST_LIBCPP_ASSERT_FAILURE(cc.back(), "deque::back called on an empty deque");
35+
TEST_LIBCPP_ASSERT_FAILURE(cc[0], "deque::operator[] index out of bounds");
36+
}
37+
38+
c.push_back(1);
39+
c.push_back(2);
40+
c.push_back(3);
41+
TEST_LIBCPP_ASSERT_FAILURE(c[3], "deque::operator[] index out of bounds");
42+
TEST_LIBCPP_ASSERT_FAILURE(c[100], "deque::operator[] index out of bounds");
43+
44+
// Repeat the test with a const reference to test the const overloads.
45+
{
46+
const std::deque<int>& cc = c;
47+
TEST_LIBCPP_ASSERT_FAILURE(cc[3], "deque::operator[] index out of bounds");
48+
TEST_LIBCPP_ASSERT_FAILURE(cc[100], "deque::operator[] index out of bounds");
49+
}
50+
51+
TEST_LIBCPP_ASSERT_FAILURE(c.erase(c.end()), "deque::erase(iterator) called with a non-dereferenceable iterator");
52+
TEST_LIBCPP_ASSERT_FAILURE(
53+
c.erase(c.begin() + 1, c.begin()), "deque::erase(first, last) called with an invalid range");
54+
55+
return 0;
56+
}

0 commit comments

Comments
 (0)