Tested on Linux-x64. This patch doesn't try to fix the reported sort() issue, because a) it would require undoing a throwing move operation, which is impossible. b) in order to avoid the throwing move, we would need to add a level of indirection and the scratch space for the indirect data would need to be allocated.
2017-01-13 Ville Voutilainen <ville.voutilai...@gmail.com> PR libstdc++/78389 * include/bits/list.tcc (merge(list&&)): Adjust list sizes if the comparator throws. (merge(list&&, _StrictWeakOrdering)): Likewise. * testsuite/23_containers/list/operations/78389.cc: New.
diff --git a/libstdc++-v3/include/bits/list.tcc b/libstdc++-v3/include/bits/list.tcc index c4f397f..fabbe7b 100644 --- a/libstdc++-v3/include/bits/list.tcc +++ b/libstdc++-v3/include/bits/list.tcc @@ -386,20 +386,29 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER iterator __last1 = end(); iterator __first2 = __x.begin(); iterator __last2 = __x.end(); - while (__first1 != __last1 && __first2 != __last2) - if (*__first2 < *__first1) - { - iterator __next = __first2; - _M_transfer(__first1, __first2, ++__next); - __first2 = __next; - } - else - ++__first1; - if (__first2 != __last2) - _M_transfer(__last1, __first2, __last2); + size_t __orig_size = __x.size(); + __try { + while (__first1 != __last1 && __first2 != __last2) + if (*__first2 < *__first1) + { + iterator __next = __first2; + _M_transfer(__first1, __first2, ++__next); + __first2 = __next; + } + else + ++__first1; + if (__first2 != __last2) + _M_transfer(__last1, __first2, __last2); - this->_M_inc_size(__x._M_get_size()); - __x._M_set_size(0); + this->_M_inc_size(__x._M_get_size()); + __x._M_set_size(0); + } + __catch(...) + { + size_t __dist = distance(__first2, __last2); + this->_M_inc_size(__dist); + __x._M_set_size(__orig_size - __dist); + } } } @@ -423,20 +432,30 @@ _GLIBCXX_BEGIN_NAMESPACE_CONTAINER iterator __last1 = end(); iterator __first2 = __x.begin(); iterator __last2 = __x.end(); - while (__first1 != __last1 && __first2 != __last2) - if (__comp(*__first2, *__first1)) - { - iterator __next = __first2; - _M_transfer(__first1, __first2, ++__next); - __first2 = __next; - } - else - ++__first1; - if (__first2 != __last2) - _M_transfer(__last1, __first2, __last2); - - this->_M_inc_size(__x._M_get_size()); - __x._M_set_size(0); + size_t __orig_size = __x.size(); + __try + { + while (__first1 != __last1 && __first2 != __last2) + if (__comp(*__first2, *__first1)) + { + iterator __next = __first2; + _M_transfer(__first1, __first2, ++__next); + __first2 = __next; + } + else + ++__first1; + if (__first2 != __last2) + _M_transfer(__last1, __first2, __last2); + + this->_M_inc_size(__x._M_get_size()); + __x._M_set_size(0); + } + __catch(...) + { + size_t __dist = distance(__first2, __last2); + this->_M_inc_size(__dist); + __x._M_set_size(__orig_size - __dist); + } } } diff --git a/libstdc++-v3/testsuite/23_containers/list/operations/78389.cc b/libstdc++-v3/testsuite/23_containers/list/operations/78389.cc new file mode 100644 index 0000000..3fa9501 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/list/operations/78389.cc @@ -0,0 +1,73 @@ +// { dg-do run { target c++11 } } + +// Copyright (C) 2017 Free Software Foundation, Inc. +// +// This file is part of the GNU ISO C++ Library. This library is free +// software; you can redistribute it and/or modify it under the +// terms of the GNU General Public License as published by the +// Free Software Foundation; either version 3, or (at your option) +// any later version. + +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License along +// with this library; see the file COPYING3. If not see +// <http://www.gnu.org/licenses/>. + +// 23.2.2.4 list operations [lib.list.ops] + +#include <testsuite_hooks.h> +#include <list> + +#include <list> + +struct ThrowingComparator +{ + unsigned int throw_after = 0; + unsigned int count = 0; + bool operator()(int, int) { + if (++count >= throw_after) { + throw 666; + } + return true; + } +}; + +struct X +{ + X() = default; + X(int) {} +}; + +unsigned int throw_after_X = 0; +unsigned int count_X = 0; + +bool operator<(const X&, const X&) { + if (++count_X >= throw_after_X) { + throw 666; + } + return true; +} + + +int main() +{ + std::list<int> a{1, 2, 3, 4}; + std::list<int> b{5, 6, 7, 8, 9, 10, 11, 12}; + try { + a.merge(b, ThrowingComparator{5}); + } catch (...) { + } + VERIFY(a.size() == 8 && b.size() == 4); + std::list<X> ax{1, 2, 3, 4}; + std::list<X> bx{5, 6, 7, 8, 9, 10, 11, 12}; + throw_after_X = 5; + try { + ax.merge(bx); + } catch (...) { + } + VERIFY(ax.size() == 8 && bx.size() == 4); +}