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 }