Skip to content

Commit f31eb2e

Browse files
committed
[libc++][hardening] Add iterator validity checks on unordered containers
These are simply null checks, so use _LIBCPP_ASSERT_NON_NULL. This allows us to restore a bunch of the old debug tests. I've extended them to also cover the const iterators, as those run through different codepaths than the const ones. This does the easier (and less important) half of #80212.
1 parent 730f498 commit f31eb2e

17 files changed

+585
-371
lines changed

libcxx/include/__hash_table

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,13 +284,21 @@ public:
284284

285285
_LIBCPP_HIDE_FROM_ABI __hash_iterator() _NOEXCEPT : __node_(nullptr) {}
286286

287-
_LIBCPP_HIDE_FROM_ABI reference operator*() const { return __node_->__upcast()->__get_value(); }
287+
_LIBCPP_HIDE_FROM_ABI reference operator*() const {
288+
_LIBCPP_ASSERT_NON_NULL(
289+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container iterator");
290+
return __node_->__upcast()->__get_value();
291+
}
288292

289293
_LIBCPP_HIDE_FROM_ABI pointer operator->() const {
294+
_LIBCPP_ASSERT_NON_NULL(
295+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container iterator");
290296
return pointer_traits<pointer>::pointer_to(__node_->__upcast()->__get_value());
291297
}
292298

293299
_LIBCPP_HIDE_FROM_ABI __hash_iterator& operator++() {
300+
_LIBCPP_ASSERT_NON_NULL(
301+
__node_ != nullptr, "Attempted to increment a non-incrementable unordered container iterator");
294302
__node_ = __node_->__next_;
295303
return *this;
296304
}
@@ -345,12 +353,20 @@ public:
345353

346354
_LIBCPP_HIDE_FROM_ABI __hash_const_iterator(const __non_const_iterator& __x) _NOEXCEPT : __node_(__x.__node_) {}
347355

348-
_LIBCPP_HIDE_FROM_ABI reference operator*() const { return __node_->__upcast()->__get_value(); }
356+
_LIBCPP_HIDE_FROM_ABI reference operator*() const {
357+
_LIBCPP_ASSERT_NON_NULL(
358+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container const_iterator");
359+
return __node_->__upcast()->__get_value();
360+
}
349361
_LIBCPP_HIDE_FROM_ABI pointer operator->() const {
362+
_LIBCPP_ASSERT_NON_NULL(
363+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container const_iterator");
350364
return pointer_traits<pointer>::pointer_to(__node_->__upcast()->__get_value());
351365
}
352366

353367
_LIBCPP_HIDE_FROM_ABI __hash_const_iterator& operator++() {
368+
_LIBCPP_ASSERT_NON_NULL(
369+
__node_ != nullptr, "Attempted to increment a non-incrementable unordered container const_iterator");
354370
__node_ = __node_->__next_;
355371
return *this;
356372
}
@@ -400,13 +416,21 @@ public:
400416

401417
_LIBCPP_HIDE_FROM_ABI __hash_local_iterator() _NOEXCEPT : __node_(nullptr) {}
402418

403-
_LIBCPP_HIDE_FROM_ABI reference operator*() const { return __node_->__upcast()->__get_value(); }
419+
_LIBCPP_HIDE_FROM_ABI reference operator*() const {
420+
_LIBCPP_ASSERT_NON_NULL(
421+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container local_iterator");
422+
return __node_->__upcast()->__get_value();
423+
}
404424

405425
_LIBCPP_HIDE_FROM_ABI pointer operator->() const {
426+
_LIBCPP_ASSERT_NON_NULL(
427+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container local_iterator");
406428
return pointer_traits<pointer>::pointer_to(__node_->__upcast()->__get_value());
407429
}
408430

409431
_LIBCPP_HIDE_FROM_ABI __hash_local_iterator& operator++() {
432+
_LIBCPP_ASSERT_NON_NULL(
433+
__node_ != nullptr, "Attempted to increment a non-incrementable unordered container local_iterator");
410434
__node_ = __node_->__next_;
411435
if (__node_ != nullptr && std::__constrain_hash(__node_->__hash(), __bucket_count_) != __bucket_)
412436
__node_ = nullptr;
@@ -475,13 +499,21 @@ public:
475499
__bucket_(__x.__bucket_),
476500
__bucket_count_(__x.__bucket_count_) {}
477501

478-
_LIBCPP_HIDE_FROM_ABI reference operator*() const { return __node_->__upcast()->__get_value(); }
502+
_LIBCPP_HIDE_FROM_ABI reference operator*() const {
503+
_LIBCPP_ASSERT_NON_NULL(
504+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container const_local_iterator");
505+
return __node_->__upcast()->__get_value();
506+
}
479507

480508
_LIBCPP_HIDE_FROM_ABI pointer operator->() const {
509+
_LIBCPP_ASSERT_NON_NULL(
510+
__node_ != nullptr, "Attempted to dereference a non-dereferenceable unordered container const_local_iterator");
481511
return pointer_traits<pointer>::pointer_to(__node_->__upcast()->__get_value());
482512
}
483513

484514
_LIBCPP_HIDE_FROM_ABI __hash_const_local_iterator& operator++() {
515+
_LIBCPP_ASSERT_NON_NULL(
516+
__node_ != nullptr, "Attempted to increment a non-incrementable unordered container const_local_iterator");
485517
__node_ = __node_->__next_;
486518
if (__node_ != nullptr && std::__constrain_hash(__node_->__hash(), __bucket_count_) != __bucket_)
487519
__node_ = nullptr;

libcxx/test/libcxx/containers/unord/unord.map/debug.iterator.dereference.pass.cpp

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
// Dereference non-dereferenceable iterator.
1212

13-
// REQUIRES: has-unix-headers
14-
// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03
13+
// REQUIRES: has-unix-headers, libcpp-hardening-mode={{extensive|debug}}
14+
// UNSUPPORTED: c++03
15+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
1516

1617
#include <unordered_map>
1718
#include <string>
@@ -20,22 +21,32 @@
2021
#include "min_allocator.h"
2122

2223
int main(int, char**) {
23-
{
24-
typedef std::unordered_map<int, std::string> C;
25-
C c;
26-
c.insert(std::make_pair(1, "one"));
27-
C::iterator i = c.end();
28-
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container iterator");
29-
}
30-
31-
{
32-
typedef std::unordered_map<int, std::string, std::hash<int>, std::equal_to<int>,
33-
min_allocator<std::pair<const int, std::string>>> C;
34-
C c;
35-
c.insert(std::make_pair(1, "one"));
36-
C::iterator i = c.end();
37-
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container iterator");
38-
}
39-
40-
return 0;
24+
{
25+
typedef std::unordered_map<int, std::string> C;
26+
C c;
27+
c.insert(std::make_pair(1, "one"));
28+
C::iterator i = c.end();
29+
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container iterator");
30+
C::const_iterator i2 = c.cend();
31+
TEST_LIBCPP_ASSERT_FAILURE(
32+
*i2, "Attempted to dereference a non-dereferenceable unordered container const_iterator");
33+
}
34+
35+
{
36+
typedef std::unordered_map<int,
37+
std::string,
38+
std::hash<int>,
39+
std::equal_to<int>,
40+
min_allocator<std::pair<const int, std::string>>>
41+
C;
42+
C c;
43+
c.insert(std::make_pair(1, "one"));
44+
C::iterator i = c.end();
45+
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container iterator");
46+
C::const_iterator i2 = c.cend();
47+
TEST_LIBCPP_ASSERT_FAILURE(
48+
*i2, "Attempted to dereference a non-dereferenceable unordered container const_iterator");
49+
}
50+
51+
return 0;
4152
}

libcxx/test/libcxx/containers/unord/unord.map/debug.iterator.increment.pass.cpp

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
// Increment iterator past end.
1212

13-
// REQUIRES: has-unix-headers
14-
// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03
13+
// REQUIRES: has-unix-headers, libcpp-hardening-mode={{extensive|debug}}
14+
// UNSUPPORTED: c++03
15+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
1516

1617
#include <unordered_map>
1718
#include <cassert>
@@ -21,26 +22,38 @@
2122
#include "min_allocator.h"
2223

2324
int main(int, char**) {
24-
{
25-
typedef std::unordered_map<int, std::string> C;
26-
C c;
27-
c.insert(std::make_pair(1, "one"));
28-
C::iterator i = c.begin();
29-
++i;
30-
assert(i == c.end());
31-
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container iterator");
32-
}
33-
34-
{
35-
typedef std::unordered_map<int, std::string, std::hash<int>, std::equal_to<int>,
36-
min_allocator<std::pair<const int, std::string>>> C;
37-
C c;
38-
c.insert(std::make_pair(1, "one"));
39-
C::iterator i = c.begin();
40-
++i;
41-
assert(i == c.end());
42-
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container iterator");
43-
}
25+
{
26+
typedef std::unordered_map<int, std::string> C;
27+
C c;
28+
c.insert(std::make_pair(1, "one"));
29+
C::iterator i = c.begin();
30+
++i;
31+
assert(i == c.end());
32+
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container iterator");
33+
C::const_iterator i2 = c.cbegin();
34+
++i2;
35+
assert(i2 == c.cend());
36+
TEST_LIBCPP_ASSERT_FAILURE(++i2, "Attempted to increment a non-incrementable unordered container const_iterator");
37+
}
38+
39+
{
40+
typedef std::unordered_map<int,
41+
std::string,
42+
std::hash<int>,
43+
std::equal_to<int>,
44+
min_allocator<std::pair<const int, std::string>>>
45+
C;
46+
C c;
47+
c.insert(std::make_pair(1, "one"));
48+
C::iterator i = c.begin();
49+
++i;
50+
assert(i == c.end());
51+
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container iterator");
52+
C::const_iterator i2 = c.cbegin();
53+
++i2;
54+
assert(i2 == c.cend());
55+
TEST_LIBCPP_ASSERT_FAILURE(++i2, "Attempted to increment a non-incrementable unordered container const_iterator");
56+
}
4457

4558
return 0;
4659
}

libcxx/test/libcxx/containers/unord/unord.map/debug.local_iterator.dereference.pass.cpp

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
// Dereference non-dereferenceable iterator.
1212

13-
// REQUIRES: has-unix-headers
14-
// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03
13+
// REQUIRES: has-unix-headers, libcpp-hardening-mode={{extensive|debug}}
14+
// UNSUPPORTED: c++03
15+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
1516

1617
#include <unordered_map>
1718
#include <string>
@@ -20,20 +21,30 @@
2021
#include "min_allocator.h"
2122

2223
int main(int, char**) {
23-
{
24-
typedef std::unordered_map<int, std::string> C;
25-
C c(1);
26-
C::local_iterator i = c.end(0);
27-
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container local_iterator");
28-
}
29-
30-
{
31-
typedef std::unordered_map<int, std::string, std::hash<int>, std::equal_to<int>,
32-
min_allocator<std::pair<const int, std::string>>> C;
33-
C c(1);
34-
C::local_iterator i = c.end(0);
35-
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container local_iterator");
36-
}
37-
38-
return 0;
24+
{
25+
typedef std::unordered_map<int, std::string> C;
26+
C c(1);
27+
C::local_iterator i = c.end(0);
28+
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container local_iterator");
29+
C::const_local_iterator i2 = c.cend(0);
30+
TEST_LIBCPP_ASSERT_FAILURE(
31+
*i2, "Attempted to dereference a non-dereferenceable unordered container const_local_iterator");
32+
}
33+
34+
{
35+
typedef std::unordered_map<int,
36+
std::string,
37+
std::hash<int>,
38+
std::equal_to<int>,
39+
min_allocator<std::pair<const int, std::string>>>
40+
C;
41+
C c(1);
42+
C::local_iterator i = c.end(0);
43+
TEST_LIBCPP_ASSERT_FAILURE(*i, "Attempted to dereference a non-dereferenceable unordered container local_iterator");
44+
C::const_local_iterator i2 = c.cend(0);
45+
TEST_LIBCPP_ASSERT_FAILURE(
46+
*i2, "Attempted to dereference a non-dereferenceable unordered container const_local_iterator");
47+
}
48+
49+
return 0;
3950
}

libcxx/test/libcxx/containers/unord/unord.map/debug.local_iterator.increment.pass.cpp

Lines changed: 44 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010

1111
// Increment local_iterator past end.
1212

13-
// REQUIRES: has-unix-headers
14-
// UNSUPPORTED: !libcpp-has-legacy-debug-mode, c++03
13+
// REQUIRES: has-unix-headers, libcpp-hardening-mode={{extensive|debug}}
14+
// UNSUPPORTED: c++03
15+
// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
1516

1617
#include <unordered_map>
1718
#include <string>
@@ -21,29 +22,45 @@
2122
#include "min_allocator.h"
2223

2324
int main(int, char**) {
24-
{
25-
typedef std::unordered_map<int, std::string> C;
26-
C c;
27-
c.insert(std::make_pair(42, std::string()));
28-
C::size_type b = c.bucket(42);
29-
C::local_iterator i = c.begin(b);
30-
assert(i != c.end(b));
31-
++i;
32-
assert(i == c.end(b));
33-
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container local_iterator");
34-
}
35-
36-
{
37-
typedef std::unordered_map<int, std::string, std::hash<int>, std::equal_to<int>,
38-
min_allocator<std::pair<const int, std::string>>> C;
39-
C c({{42, std::string()}});
40-
C::size_type b = c.bucket(42);
41-
C::local_iterator i = c.begin(b);
42-
assert(i != c.end(b));
43-
++i;
44-
assert(i == c.end(b));
45-
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container local_iterator");
46-
}
47-
48-
return 0;
25+
{
26+
typedef std::unordered_map<int, std::string> C;
27+
C c;
28+
c.insert(std::make_pair(42, std::string()));
29+
C::size_type b = c.bucket(42);
30+
C::local_iterator i = c.begin(b);
31+
assert(i != c.end(b));
32+
++i;
33+
assert(i == c.end(b));
34+
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container local_iterator");
35+
C::const_local_iterator i2 = c.cbegin(b);
36+
assert(i2 != c.cend(b));
37+
++i2;
38+
assert(i2 == c.cend(b));
39+
TEST_LIBCPP_ASSERT_FAILURE(
40+
++i2, "Attempted to increment a non-incrementable unordered container const_local_iterator");
41+
}
42+
43+
{
44+
typedef std::unordered_map<int,
45+
std::string,
46+
std::hash<int>,
47+
std::equal_to<int>,
48+
min_allocator<std::pair<const int, std::string>>>
49+
C;
50+
C c({{42, std::string()}});
51+
C::size_type b = c.bucket(42);
52+
C::local_iterator i = c.begin(b);
53+
assert(i != c.end(b));
54+
++i;
55+
assert(i == c.end(b));
56+
TEST_LIBCPP_ASSERT_FAILURE(++i, "Attempted to increment a non-incrementable unordered container local_iterator");
57+
C::const_local_iterator i2 = c.cbegin(b);
58+
assert(i2 != c.cend(b));
59+
++i2;
60+
assert(i2 == c.cend(b));
61+
TEST_LIBCPP_ASSERT_FAILURE(
62+
++i2, "Attempted to increment a non-incrementable unordered container const_local_iterator");
63+
}
64+
65+
return 0;
4966
}

0 commit comments

Comments
 (0)