https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92577

            Bug ID: 92577
           Summary: Undefined behavior when using std::map with a noexcept
                    allocator
           Product: gcc
           Version: 9.2.1
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: lucas.bader at sap dot com
  Target Milestone: ---

Created attachment 47296
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=47296&action=edit
minimal code example

Using std::map with an allocator with a noexcept specifier leads to undefined
behavior when constructing a node in-place.

Compiled with GCC9 this results in the generation of a ud2 instruction that is
encountered when nullptr be retured by the allocation function:

$ g++ -std=c++17 -O3 allocator_ub.cpp
$ ./a.out
[2]    114342 illegal hardware instruction (core dumped)  ./a.out

$ objdump -d a.out
0000000000400780
<_ZNSt8_Rb_treeIiSt4pairIKiiESt10_Select1stIS2_ESt4lessIiE10mallocatorIS2_EE17_M_emplace_uniqueIJS0_IiiEEEES0_ISt17_Rb_tree_iteratorIS2_EbEDpOT_>:
...
4008b3:       0f 0b                   ud2

The corresponding stack trace in gdb is:

#0  0x00000000004008b3 in std::pair<std::_Rb_tree_iterator<std::pair<int const,
int> >, bool> std::_Rb_tree<int, std::pair<int const, int>,
std::_Select1st<std::pair<int const, int> >, std::less<int>,
mallocator<std::pair<int const, int> > >::_M_emplace_unique<std::pair<int, int>
>(std::pair<int, int>&&) ()
#1  0x0000000000400632 in main ()

According to the C++17 working draft (8.3.4 paragraph 16):

"If the allocation function has a non-throwing exception specification, it
returns null to
indicate failure to allocate storage and a non-null pointer otherwise. [...]
If the allocation function is a non-allocating form (21.6.2.3) that returns
null, the behavior is undefined."

This is also supported by cppreference.com
(https://en.cppreference.com/w/cpp/language/new) with:

"If the standard placement allocation function returns a null pointer, which is
possible if the user passes a null pointer as the argument, the behavior is
undefined. (since C++17)"

The std::map implementation should handle this appropriately or prohibit using
it with an allocator that is defined noexcept.
When using an allocation function with the noexcept specifier (and thus
returning nullptr on failure), nullptr is passed to placement new (in
stl_tree.h) via the following code:

  2406     _M_emplace_unique(_Args&&... __args)
  2407     {
  2408      _Link_type __z = _M_create_node(std::forward<_Args>(__args)...);


   628     _M_create_node(_Args&&... __args)
   629     {
   630      _Link_type __tmp = _M_get_node();
   631      _M_construct_node(__tmp, std::forward<_Args>(__args)...);


   579     _M_get_node()
   580     { return _Alloc_traits::allocate(_M_get_Node_allocator(), 1); }


   609     _M_construct_node(_Link_type __node, _Args&&... __args)
   610     {
   611      __try
   612        {
   613         ::new(__node) _Rb_tree_node<_Val>;
   614         _Alloc_traits::construct(_M_get_Node_allocator(),
   615                      __node->_M_valptr(),
   616                      std::forward<_Args>(__args)...);
   617        }
   618      __catch(...)
   619        {
   620         __node->~_Rb_tree_node<_Val>();
   621         _M_put_node(__node);
   622         __throw_exception_again;
   623        }
   624     }

Reply via email to