On Sat, 16 Nov 2024, 01:21 Jonathan Wakely, <jwak...@redhat.com> wrote:
> On Sat, 16 Nov 2024 at 01:09, Jonathan Wakely <jwak...@redhat.com> wrote: > > > > While working on fancy pointer support for the linked lists I noticed > > they didn't have any debug assertions. This adds the obvious non-empty > > assertions to front(), back(), pop_front() and pop_back(). > > > > For the pop members, adding an assertion to the underlying function that > > erases a member means it also check erase(end()), which is always > > invalid, and erase(begin()) on an empty list. For those erase members we > > can also add a check so that we return without doing anything if the > > assertion is disabled, but would have failed had it been enabled. > > > > libstdc++-v3/ChangeLog: > > > > * include/bits/forward_list.h (forward_list::front): Add > > non-empty assertions. > > * include/bits/forward_list.tcc (_Fwd_list_base::_M_erase_after): > > Likewise. Return immediately if argument is invalid. > > * include/bits/stl_list.h (list::front, list::back): Add > > non-empty assertions. > > (list::_M_erase): Likewise. Return immediately if argument is > > invalid. > > --- > > > > Tested x86_64-linux. > > > > As pull request: https://forge.sourceware.org/gcc/gcc-TEST/pulls/26 > > > > libstdc++-v3/include/bits/forward_list.h | 3 +++ > > libstdc++-v3/include/bits/forward_list.tcc | 6 ++++++ > > libstdc++-v3/include/bits/stl_list.h | 19 +++++++++++++++++-- > > 3 files changed, 26 insertions(+), 2 deletions(-) > > > > diff --git a/libstdc++-v3/include/bits/forward_list.h > b/libstdc++-v3/include/bits/forward_list.h > > index c9238cef96f..3fac657518c 100644 > > --- a/libstdc++-v3/include/bits/forward_list.h > > +++ b/libstdc++-v3/include/bits/forward_list.h > > @@ -42,6 +42,7 @@ > > #include <bits/allocator.h> > > #include <ext/alloc_traits.h> > > #include <ext/aligned_buffer.h> > > +#include <debug/assertions.h> > > #if __glibcxx_ranges_to_container // C++ >= 23 > > # include <bits/ranges_base.h> // ranges::begin, ranges::distance etc. > > # include <bits/ranges_util.h> // ranges::subrange > > @@ -884,6 +885,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER > > reference > > front() > > { > > + __glibcxx_requires_nonempty(); > > _Node* __front = > static_cast<_Node*>(this->_M_impl._M_head._M_next); > > return *__front->_M_valptr(); > > } > > @@ -896,6 +898,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER > > const_reference > > front() const > > { > > + __glibcxx_requires_nonempty(); > > _Node* __front = > static_cast<_Node*>(this->_M_impl._M_head._M_next); > > return *__front->_M_valptr(); > > } > > diff --git a/libstdc++-v3/include/bits/forward_list.tcc > b/libstdc++-v3/include/bits/forward_list.tcc > > index 9750c7c0502..50acdb9f26b 100644 > > --- a/libstdc++-v3/include/bits/forward_list.tcc > > +++ b/libstdc++-v3/include/bits/forward_list.tcc > > @@ -63,6 +63,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER > > _Fwd_list_base<_Tp, _Alloc>:: > > _M_erase_after(_Fwd_list_node_base* __pos) > > { > > + if (__pos == nullptr || __pos->_M_next == nullptr) > [[__unlikely__]] > > + { > > + __glibcxx_assert(__pos != nullptr && __pos->_M_next != > nullptr); > > + return nullptr; > > + } > > + > > _Node* __curr = static_cast<_Node*>(__pos->_M_next); > > __pos->_M_next = __curr->_M_next; > > _Node_alloc_traits::destroy(_M_get_Node_allocator(), > > diff --git a/libstdc++-v3/include/bits/stl_list.h > b/libstdc++-v3/include/bits/stl_list.h > > index 7deb04b4bfe..d70ba90b8fa 100644 > > --- a/libstdc++-v3/include/bits/stl_list.h > > +++ b/libstdc++-v3/include/bits/stl_list.h > > @@ -59,6 +59,7 @@ > > > > #include <bits/concept_check.h> > > #include <ext/alloc_traits.h> > > +#include <debug/assertions.h> > > #if __cplusplus >= 201103L > > #include <initializer_list> > > #include <bits/allocated_ptr.h> > > @@ -1249,7 +1250,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > _GLIBCXX_NODISCARD > > reference > > front() _GLIBCXX_NOEXCEPT > > - { return *begin(); } > > + { > > + __glibcxx_requires_nonempty(); > > + return *begin(); > > + } > > > > /** > > * Returns a read-only (constant) reference to the data at the > first > > @@ -1258,7 +1262,10 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > _GLIBCXX_NODISCARD > > const_reference > > front() const _GLIBCXX_NOEXCEPT > > - { return *begin(); } > > + { > > + __glibcxx_requires_nonempty(); > > + return *begin(); > > + } > > > > /** > > * Returns a read/write reference to the data at the last element > > @@ -1268,6 +1275,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > reference > > back() _GLIBCXX_NOEXCEPT > > { > > + __glibcxx_requires_nonempty(); > > iterator __tmp = end(); > > --__tmp; > > return *__tmp; > > @@ -1281,6 +1289,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > const_reference > > back() const _GLIBCXX_NOEXCEPT > > { > > + __glibcxx_requires_nonempty(); > > const_iterator __tmp = end(); > > --__tmp; > > return *__tmp; > > @@ -2132,6 +2141,12 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11 > > void > > _M_erase(iterator __position) _GLIBCXX_NOEXCEPT > > { > > + if (__builtin_expect(empty(), 0)) > > + { > > + __glibcxx_requires_nonempty(); > > + return; > > + } > > Hmm, I'm having second thoughts about the "return without doing > anything part now. > For this simple test: > > #include <list> > > int main() > { > std::list<int> l; > l.erase(l.begin()); > } > > Currently it crashes (bad), but with -O1 there's a nice warning: > > /usr/include/c++/14/bits/new_allocator.h:172:33: warning: ‘void > operator delete(void*, std::size_t)’ called on unallocated object ‘l’ > [-Wfree-nonheap-object] > > And Asan can diagnose it too. > > Adding an assertion is definitely an improvement, as it avoids the > crash . But returning when the assertion is disabled, so that the > function is a no-op, means that the warning about freeing a null > pointer goes away, because the compiler can see it's never reached. > And now Asan can't diagnose it. > > I think on balance, making it a no-op and avoiding arbitrary UB is > better. The warning is only possible in trivial cases where the > compiler can see the pointer is definitely null. In more realistic > code, there will be no warning, and UB, so turning it into a silent > no-op does seem safer. If you want to detect the bug, enable > assertions. > > What do others think? Better to add the assertion but leave the UB > present when assertions are disabled, or add the assertion and > silently remove the UB when assertions are enabled? > ^^ *disabled* ! I clearly need to get more sleep... > > > > > > + > > this->_M_dec_size(1); > > __position._M_node->_M_unhook(); > > _Node* __n = static_cast<_Node*>(__position._M_node); > > -- > > 2.47.0 > > > >