Skip to content

Commit 02797a4

Browse files
committed
libstdc++: Avoid incrementing input iterators with std::prev [PR122224]
As explained in PR libstdc++/122224 we do not make it ill-formed to call std::prev with a non-Cpp17BidirectionalIterator. Instead we just use a runtime assertion to check the std::advance precondition that the distance is not negative. This allows us to support std::prev on types which model the C++20 std::bidirectional_iterator concept but do not meet the Cpp17BidirectionalIterator requirements, e.g. iota_view's iterators. It also allows us to support std::prev(iter, -1) which is admittedly weird, but there's no reason it shouldn't be equivalent to std::next(iter), which is perfectly fine to use on non-bidirectional iterators. In other words, "reverse decrementing" is valid for non-bidirectional iterators. However, the current implementation of std::advance for non-bidirectional iterators uses a loop that does `while (n--) ++i;` which assumes that n is not negative and so will eventually reach zero. When the assertion for the precondition is not enabled, incrementing the iterator while n is non-zero means that using std::prev(iter) or std::next(iter, -1) on a non-bidirectional iterator will keep incrementing the iterator until n reaches INT_MIN, overflows, and then keeps decrementing until it eventually reaches zero. Incrementing most iterators that many times will cause memory safety errors long before the integer reaches zero and terminates the loop. This commit changes the loop to use `while (n-- > 0)` which means that the loop doesn't execute at all if a negative n is used. We still consider such calls to be erroneous, but when the precondition isn't checked by an assertion, the function now has no effects. The undefined behaviour resulting from incrementing the iterator is prevented. libstdc++-v3/ChangeLog: PR libstdc++/122224 * include/bits/stl_iterator_base_funcs.h (prev): Compare distance as n > 0 instead of n != 0. * testsuite/24_iterators/range_operations/122224.cc: New test. Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
1 parent 6c01778 commit 02797a4

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

libstdc++-v3/include/bits/stl_iterator_base_funcs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ namespace __detail
201201
// concept requirements
202202
__glibcxx_function_requires(_InputIteratorConcept<_InputIterator>)
203203
__glibcxx_assert(__n >= 0);
204-
while (__n--)
204+
while (__n-- > 0)
205205
++__i;
206206
}
207207

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// { dg-do run { target c++11 } }
2+
// { dg-add-options no_pch }
3+
4+
// Undefine these if present in runtest flags.
5+
#undef _GLIBCXX_ASSERTIONS
6+
#undef _GLIBCXX_DEBUG
7+
8+
// Prevent assertions from being automatically enabled at -O0
9+
#define _GLIBCXX_NO_ASSERTIONS
10+
11+
#include <iterator>
12+
#include <testsuite_iterators.h>
13+
#include <testsuite_hooks.h>
14+
15+
template<typename Container>
16+
void
17+
test_advance()
18+
{
19+
int a[] = { 1, 2, 3 };
20+
Container c(a);
21+
auto iter = c.begin();
22+
23+
// This call violates the precondition for std::advance,
24+
// but with assertions disabled we do not diagnose it.
25+
std::advance(iter, -1);
26+
27+
// However we do guarantee that erroneously decrementing
28+
// an input iterator is a no-op and does no harm.
29+
VERIFY( *iter == 1 );
30+
31+
++iter;
32+
std::advance(iter, -999);
33+
VERIFY( *iter == 2 );
34+
35+
std::advance(iter, 0);
36+
VERIFY( *iter == 2 );
37+
std::advance(iter, 1);
38+
VERIFY( *iter == 3 );
39+
}
40+
41+
template<typename Container>
42+
void
43+
test_prev()
44+
{
45+
int a[] = { 1, 2, 3 };
46+
Container c(a);
47+
auto iter = c.begin();
48+
49+
// This calls std::advance(iter, -1), which violates the precondition.
50+
iter = std::prev(iter);
51+
52+
// As above, we turn the std::prev call into a no-op.
53+
VERIFY( *iter == 1 );
54+
55+
++iter;
56+
iter = std::prev(iter, 999);
57+
VERIFY( *iter == 2 );
58+
59+
iter = std::prev(iter, 0);
60+
VERIFY( *iter == 2 );
61+
iter = std::prev(iter, -1);
62+
VERIFY( *iter == 3 );
63+
}
64+
65+
template<typename Container>
66+
void
67+
test_next()
68+
{
69+
int a[] = { 1, 2, 3 };
70+
Container c(a);
71+
auto iter = c.begin();
72+
73+
// This calls std::advance(iter, -1), which violates the precondition.
74+
iter = std::next(iter, -1);
75+
76+
// As above, we turn the std::prev call into a no-op.
77+
VERIFY( *iter == 1 );
78+
79+
++iter;
80+
iter = std::next(iter, -999);
81+
VERIFY( *iter == 2 );
82+
83+
iter = std::next(iter, 0);
84+
VERIFY( *iter == 2 );
85+
iter = std::next(iter);
86+
VERIFY( *iter == 3 );
87+
}
88+
89+
int main()
90+
{
91+
using InputContainer = __gnu_test::input_container<int>;
92+
test_advance<InputContainer>();
93+
test_prev<InputContainer>();
94+
test_next<InputContainer>();
95+
96+
using ForwardContainer = __gnu_test::forward_container<int>;
97+
test_advance<ForwardContainer>();
98+
test_prev<ForwardContainer>();
99+
test_next<ForwardContainer>();
100+
}

0 commit comments

Comments
 (0)