How do I get an error from `pop_back()` if `size()` is 0?

Viewed 179

I was explaining to a coworker why we have small test with sanitizers on them. He asked about popping a vector too many times and if it was an exception, assert, UB and which sanitizer catches it

It appears NONE catches them. Address and memory will if you call back() after popping too many times but if you pop and do size() you get a large invalid value due to wrapping

Is there a way I can get an assert or exception or runtime termination when I pop too many times? I really thought a debug build without sanitizers would have caught that (with an assert or exception)

I use clang sanitizer but build options with gcc will also be helpful

2 Answers

Both libstdc++ and libc++ have a "debug mode" with assertions, that can be enabled using:

  • -D_GLIBCXX_DEBUG for libstdc++
  • -D_LIBCPP_DEBUG for libc++

Also -fsanitize=undefined appears to catch it, but the error message is much more cryptic.

The sanitizers may need some annotations to do a good job on library types. g++ -fsanitize=address -D_GLIBCXX_SANITIZE_VECTOR (documentation), clang++ -stdlib=libc++ -fsanitize=address or clang++ -stdlib=libstdc++ -fsanitize=address -D_GLIBCXX_SANITIZE_VECTOR -D__SANITIZE_ADDRESS__ (the need for this last macro is a bug) all detect the issue and print a message

=================================================================
==12312==ERROR: AddressSanitizer: bad parameters to __sanitizer_annotate_contiguous_container:
      beg     : 0x602000000010
      end     : 0x602000000014
      old_mid : 0x602000000010
      new_mid : 0x60200000000c
    #0 0x7f6a9db3b707 in __sanitizer_annotate_contiguous_container ../../../../src/libsanitizer/asan/asan_poisoning.cpp:362
    #1 0x55b9dcd1fc41 in std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Asan<std::allocator<int> >::_S_adjust(std::_Vector_base<int, std::allocator<int> >::_Vector_impl&, int*, int*) (/tmp/a.out+0x1c41)
    #2 0x55b9dcd1fb66 in std::_Vector_base<int, std::allocator<int> >::_Vector_impl::_Asan<std::allocator<int> >::_S_shrink(std::_Vector_base<int, std::allocator<int> >::_Vector_impl&, unsigned long) (/tmp/a.out+0x1b66)
    #3 0x55b9dcd1f6c4 in std::vector<int, std::allocator<int> >::pop_back() (/tmp/a.out+0x16c4)
    #4 0x55b9dcd1f36d in main (/tmp/a.out+0x136d)
    #5 0x7f6a9d57fe49 in __libc_start_main ../csu/libc-start.c:314
    #6 0x55b9dcd1f199 in _start (/tmp/a.out+0x1199)

SUMMARY: AddressSanitizer: bad-__sanitizer_annotate_contiguous_container ../../../../src/libsanitizer/asan/asan_poisoning.cpp:362 in __sanitizer_annotate_contiguous_container
==12312==ABORTING

Note that there is no need for something as complicated as the sanitizers for this case, as mentioned in the other answer, libraries often have debug modes. For instance with libstdc++, defining _GLIBCXX_DEBUG enables the full debug mode (ABI-incompatible with normal mode)

/usr/include/c++/11/debug/vector:523:
In function:
    void std::__debug::vector<_Tp, _Allocator>::pop_back() [with _Tp = int; 
    _Allocator = std::allocator<int>]

Error: attempt to access an element in an empty container.

Objects involved in the operation:
    sequence "this" @ 0x0x7ffe20bf5f80 {
      type = std::__debug::vector<int, std::allocator<int> >;
    }

while defining _GLIBCXX_ASSERTIONS enables much lighter assertions and preserves the ABI, but gives less information about the error

/usr/include/c++/11/bits/stl_vector.h:1227: void std::vector<_Tp, _Alloc>::pop_back() [with _Tp = int; _Alloc = std::allocator<int>]: Assertion '!this->empty()' failed.

and defining _LIBCPP_DEBUG with libc++ prints

/usr/lib/llvm-11/bin/../include/c++/v1/vector:1703: _LIBCPP_ASSERT '!empty()' failed. vector::pop_back called for empty vector
Related