We got a bug report from a customer pointing out that calling promise::set_value on a moved-from promise crashes instead of throwing an exception with error code future_errc::no_state.
This fixes it, by moving the _S_check calls to *before* we deference the pointer that the calls check! This passes all tests, including the more comprehensive ones I've added as part of this commit, but I think it can wait for stage 1 anyway. We've been shipping this bug for a couple of releases already. PR libstdc++/80316 * include/std/future (_State_baseV2::_Setter::operator()): Remove _S_check calls that are done after the pointer to the shared state is already dereferenced. (_State_baseV2::_Setter<_Res, void>): Define specialization for void as partial specialization so it can be defined within the definition of _State_baseV2. (_State_baseV2::__setter): Call _S_check. (_State_baseV2::__setter(promise<void>*)): Add overload for use by promise<void>::set_value and promise<void>::set_value_at_thread_exit. (promise<T>, promise<T&>, promise<void>): Make _State a friend. (_State_baseV2::_Setter<void, void>): Remove explicit specialization. (promise<void>::set_value, promise<void>::set_value_at_thread_exit): Use new __setter overload. * testsuite/30_threads/promise/members/at_thread_exit2.cc: New test. * testsuite/30_threads/promise/members/set_exception.cc: Test promise<T&> and promise<void> specializations. * testsuite/30_threads/promise/members/set_exception2.cc: Likewise. Test for no_state error condition. * testsuite/30_threads/promise/members/set_value2.cc: Likewise.
commit 83c1e45d0759dd095c7b35d67fb5ba98dc7bf909 Author: Jonathan Wakely <jwak...@redhat.com> Date: Tue Apr 4 19:48:33 2017 +0100 PR libstdc++/80316 make promise::set_value throw no_state error PR libstdc++/80316 * include/std/future (_State_baseV2::_Setter::operator()): Remove _S_check calls that are done after the pointer to the shared state is already dereferenced. (_State_baseV2::_Setter<_Res, void>): Define specialization for void as partial specialization so it can be defined within the definition of _State_baseV2. (_State_baseV2::__setter): Call _S_check. (_State_baseV2::__setter(promise<void>*)): Add overload for use by promise<void>::set_value and promise<void>::set_value_at_thread_exit. (promise<T>, promise<T&>, promise<void>): Make _State a friend. (_State_baseV2::_Setter<void, void>): Remove explicit specialization. (promise<void>::set_value, promise<void>::set_value_at_thread_exit): Use new __setter overload. * testsuite/30_threads/promise/members/at_thread_exit2.cc: New test. * testsuite/30_threads/promise/members/set_exception.cc: Test promise<T&> and promise<void> specializations. * testsuite/30_threads/promise/members/set_exception2.cc: Likewise. Test for no_state error condition. * testsuite/30_threads/promise/members/set_value2.cc: Likewise. diff --git a/libstdc++-v3/include/std/future b/libstdc++-v3/include/std/future index cb53561..73d5a60 100644 --- a/libstdc++-v3/include/std/future +++ b/libstdc++-v3/include/std/future @@ -471,7 +471,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Used by std::promise to copy construct the result. typename promise<_Res>::_Ptr_type operator()() const { - _State_baseV2::_S_check(_M_promise->_M_future); _M_promise->_M_storage->_M_set(*_M_arg); return std::move(_M_promise->_M_storage); } @@ -486,7 +485,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Used by std::promise to move construct the result. typename promise<_Res>::_Ptr_type operator()() const { - _State_baseV2::_S_check(_M_promise->_M_future); _M_promise->_M_storage->_M_set(std::move(*_M_arg)); return std::move(_M_promise->_M_storage); } @@ -494,6 +492,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _Res* _M_arg; }; + // set void + template<typename _Res> + struct _Setter<_Res, void> + { + static_assert(is_void<_Res>::value, "Only used for promise<void>"); + + typename promise<_Res>::_Ptr_type operator()() const + { return std::move(_M_promise->_M_storage); } + + promise<_Res>* _M_promise; + }; + struct __exception_ptr_tag { }; // set exceptions @@ -503,7 +513,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // Used by std::promise to store an exception as the result. typename promise<_Res>::_Ptr_type operator()() const { - _State_baseV2::_S_check(_M_promise->_M_future); _M_promise->_M_storage->_M_error = *_M_ex; return std::move(_M_promise->_M_storage); } @@ -516,6 +525,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static _Setter<_Res, _Arg&&> __setter(promise<_Res>* __prom, _Arg&& __arg) { + _S_check(__prom->_M_future); return _Setter<_Res, _Arg&&>{ __prom, std::__addressof(__arg) }; } @@ -523,9 +533,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION static _Setter<_Res, __exception_ptr_tag> __setter(exception_ptr& __ex, promise<_Res>* __prom) { + _S_check(__prom->_M_future); return _Setter<_Res, __exception_ptr_tag>{ __prom, &__ex }; } + template<typename _Res> + static _Setter<_Res, void> + __setter(promise<_Res>* __prom) + { + _S_check(__prom->_M_future); + return _Setter<_Res, void>{ __prom }; + } + template<typename _Tp> static void _S_check(const shared_ptr<_Tp>& __p) @@ -1027,6 +1046,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef __future_base::_Result<_Res> _Res_type; typedef __future_base::_Ptr<_Res_type> _Ptr_type; template<typename, typename> friend class _State::_Setter; + friend _State; shared_ptr<_State> _M_future; _Ptr_type _M_storage; @@ -1137,6 +1157,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef __future_base::_Result<_Res&> _Res_type; typedef __future_base::_Ptr<_Res_type> _Ptr_type; template<typename, typename> friend class _State::_Setter; + friend _State; shared_ptr<_State> _M_future; _Ptr_type _M_storage; @@ -1226,6 +1247,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef __future_base::_Result<void> _Res_type; typedef __future_base::_Ptr<_Res_type> _Ptr_type; template<typename, typename> friend class _State::_Setter; + friend _State; shared_ptr<_State> _M_future; _Ptr_type _M_storage; @@ -1286,14 +1308,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return future<void>(_M_future); } // Setting the result - void set_value(); + void + set_value() + { _M_future->_M_set_result(_State::__setter(this)); } void set_exception(exception_ptr __p) { _M_future->_M_set_result(_State::__setter(__p, this)); } void - set_value_at_thread_exit(); + set_value_at_thread_exit() + { _M_future->_M_set_delayed_result(_State::__setter(this), _M_future); } void set_exception_at_thread_exit(exception_ptr __p) @@ -1303,30 +1328,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } }; - // set void - template<> - struct __future_base::_State_base::_Setter<void, void> - { - promise<void>::_Ptr_type operator()() const - { - _State_base::_S_check(_M_promise->_M_future); - return std::move(_M_promise->_M_storage); - } - - promise<void>* _M_promise; - }; - - inline void - promise<void>::set_value() - { _M_future->_M_set_result(_State::_Setter<void, void>{ this }); } - - inline void - promise<void>::set_value_at_thread_exit() - { - _M_future->_M_set_delayed_result(_State::_Setter<void, void>{this}, - _M_future); - } - template<typename _Ptr_type, typename _Fn, typename _Res> struct __future_base::_Task_setter { diff --git a/libstdc++-v3/testsuite/30_threads/promise/members/at_thread_exit2.cc b/libstdc++-v3/testsuite/30_threads/promise/members/at_thread_exit2.cc new file mode 100644 index 0000000..9429a99 --- /dev/null +++ b/libstdc++-v3/testsuite/30_threads/promise/members/at_thread_exit2.cc @@ -0,0 +1,167 @@ +// { dg-do run { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* *-*-solaris* *-*-cygwin *-*-rtems* *-*-darwin* powerpc-ibm-aix* } } +// { dg-options "-pthread" { target *-*-freebsd* *-*-dragonfly* *-*-netbsd* *-*-linux* *-*-gnu* *-*-solaris* powerpc-ibm-aix* } } +// { dg-require-effective-target c++11 } +// { dg-require-cstdint "" } +// { dg-require-gthreads "" } + +// Copyright (C) 2014-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/>. + +// Test set_value_at_thread_exit error conditions + +#include <future> +#include <testsuite_hooks.h> + +void test01() +{ + std::promise<int> p1; + p1.set_value(1); + try + { + p1.set_value_at_thread_exit(2); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::promise_already_satisfied ); + } + try + { + p1.set_exception_at_thread_exit(std::make_exception_ptr(3)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::promise_already_satisfied ); + } + + std::promise<int> p2(std::move(p1)); + try + { + p1.set_value_at_thread_exit(2); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::no_state ); + } + try + { + p1.set_exception_at_thread_exit(std::make_exception_ptr(3)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::no_state ); + } +} + +void test02() +{ + std::promise<int&> p1; + int i = 1; + p1.set_value(i); + try + { + p1.set_value_at_thread_exit(i); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::promise_already_satisfied ); + } + try + { + p1.set_exception_at_thread_exit(std::make_exception_ptr(3)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::promise_already_satisfied ); + } + + std::promise<int&> p2(std::move(p1)); + try + { + int i = 0; + p1.set_value_at_thread_exit(i); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::no_state ); + } + try + { + p1.set_exception_at_thread_exit(std::make_exception_ptr(3)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::no_state ); + } +} + +void test03() +{ + std::promise<void> p1; + int i = 0; + p1.set_value(); + try { + p1.set_value_at_thread_exit(); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::promise_already_satisfied ); + } + try + { + p1.set_exception_at_thread_exit(std::make_exception_ptr(3)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::promise_already_satisfied ); + } + + std::promise<void> p2(std::move(p1)); + try { + p1.set_value_at_thread_exit(); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::no_state ); + } + try + { + p1.set_exception_at_thread_exit(std::make_exception_ptr(3)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY( e.code() == std::future_errc::no_state ); + } +} + +int main() +{ + test01(); + test02(); + test03(); +} diff --git a/libstdc++-v3/testsuite/30_threads/promise/members/set_exception.cc b/libstdc++-v3/testsuite/30_threads/promise/members/set_exception.cc index 6ff4a34..4646e38 100644 --- a/libstdc++-v3/testsuite/30_threads/promise/members/set_exception.cc +++ b/libstdc++-v3/testsuite/30_threads/promise/members/set_exception.cc @@ -21,6 +21,7 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. +// Test that promise::set_exception stores an exception. #include <future> #include <testsuite_hooks.h> @@ -48,8 +49,56 @@ void test01() VERIFY( !f1.valid() ); } +void test02() +{ + bool test = false; + + std::promise<int&> p1; + std::future<int&> f1 = p1.get_future(); + + VERIFY( f1.valid() ); + + p1.set_exception(std::make_exception_ptr(0)); + + try + { + f1.get(); + } + catch (int) + { + test = true; + } + VERIFY( test ); + VERIFY( !f1.valid() ); +} + +void test03() +{ + bool test = false; + + std::promise<void> p1; + std::future<void> f1 = p1.get_future(); + + VERIFY( f1.valid() ); + + p1.set_exception(std::make_exception_ptr(0)); + + try + { + f1.get(); + } + catch (int) + { + test = true; + } + VERIFY( test ); + VERIFY( !f1.valid() ); +} + int main() { test01(); + test02(); + test03(); return 0; } diff --git a/libstdc++-v3/testsuite/30_threads/promise/members/set_exception2.cc b/libstdc++-v3/testsuite/30_threads/promise/members/set_exception2.cc index 1b1a066..dc2c4a8 100644 --- a/libstdc++-v3/testsuite/30_threads/promise/members/set_exception2.cc +++ b/libstdc++-v3/testsuite/30_threads/promise/members/set_exception2.cc @@ -21,10 +21,13 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. +// Test that promise::set_exception throws when required. #include <future> #include <testsuite_hooks.h> +// Check for promise_already_satisfied error conditions. + void test01() { bool test = false; @@ -83,9 +86,187 @@ void test02() VERIFY( test ); } +void test03() +{ + bool test = false; + + std::promise<int&> p1; + std::future<int&> f1 = p1.get_future(); + + p1.set_exception(std::make_exception_ptr(0)); + + try + { + p1.set_exception(std::make_exception_ptr(1)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + try + { + f1.get(); + test = false; + } + catch(int i) + { + VERIFY( i == 0 ); + } + + VERIFY( test ); +} + +void test04() +{ + bool test = false; + + std::promise<int&> p1; + std::future<int&> f1 = p1.get_future(); + + int i = 2; + p1.set_value(i); + + try + { + p1.set_exception(std::make_exception_ptr(0)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + VERIFY( test ); +} + +void test05() +{ + bool test = false; + + std::promise<void> p1; + std::future<void> f1 = p1.get_future(); + + p1.set_exception(std::make_exception_ptr(0)); + + try + { + p1.set_exception(std::make_exception_ptr(1)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + try + { + f1.get(); + test = false; + } + catch(int i) + { + VERIFY( i == 0 ); + } + + VERIFY( test ); +} + +void test06() +{ + bool test = false; + + std::promise<void> p1; + std::future<void> f1 = p1.get_future(); + + p1.set_value(); + + try + { + p1.set_exception(std::make_exception_ptr(0)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + VERIFY( test ); +} + +// Check for no_state error condition (PR libstdc++/80316) + +void test07() +{ + using namespace std; + + promise<int> p1; + promise<int> p2(std::move(p1)); + try + { + p1.set_exception(std::make_exception_ptr(1)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == make_error_code(future_errc::no_state)); + } +} + +void test08() +{ + using namespace std; + + promise<int&> p1; + promise<int&> p2(std::move(p1)); + try + { + int i = 0; + p1.set_exception(std::make_exception_ptr(1)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == make_error_code(future_errc::no_state)); + } +} + +void test09() +{ + using namespace std; + + promise<void> p1; + promise<void> p2(std::move(p1)); + try + { + p1.set_exception(std::make_exception_ptr(1)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == make_error_code(future_errc::no_state)); + } +} + int main() { test01(); test02(); + test03(); + test04(); + test05(); + test06(); + test07(); + test08(); + test09(); return 0; } diff --git a/libstdc++-v3/testsuite/30_threads/promise/members/set_value2.cc b/libstdc++-v3/testsuite/30_threads/promise/members/set_value2.cc index 0c4e87a..33c8ed9 100644 --- a/libstdc++-v3/testsuite/30_threads/promise/members/set_value2.cc +++ b/libstdc++-v3/testsuite/30_threads/promise/members/set_value2.cc @@ -21,10 +21,13 @@ // with this library; see the file COPYING3. If not see // <http://www.gnu.org/licenses/>. +// Test that promise::set_value throws when required. #include <future> #include <testsuite_hooks.h> +// Check for promise_already_satisfied error conditions. + void test01() { bool test = false; @@ -79,9 +82,298 @@ void test02() VERIFY( test ); } +void test03() +{ + bool test = false; + + std::promise<int> p1; + std::future<int> f1 = p1.get_future(); + + p1.set_exception(std::make_exception_ptr(4)); + + try + { + p1.set_value(3); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + test = false; + try + { + f1.get(); + VERIFY( false ); + } + catch (int e) + { + VERIFY(e == 4 ); + test = true; + } + + VERIFY( test ); +} + +void test04() +{ + bool test = false; + + std::promise<int&> p1; + std::future<int&> f1 = p1.get_future(); + + int i = 1; + p1.set_value(i); + + try + { + p1.set_value(i); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + VERIFY( f1.get() == 1 ); + VERIFY( test ); +} + +void test05() +{ + bool test = false; + + std::promise<int&> p1; + std::future<int&> f1 = p1.get_future(); + + int i = 3; + p1.set_value(i); + + try + { + p1.set_exception(std::make_exception_ptr(4)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + VERIFY( f1.get() == 3 ); + VERIFY( test ); +} + +void test06() +{ + bool test = false; + + std::promise<int&> p1; + std::future<int&> f1 = p1.get_future(); + + p1.set_exception(std::make_exception_ptr(4)); + + try + { + int i = 3; + p1.set_value(i); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + test = false; + try + { + f1.get(); + VERIFY( false ); + } + catch (int e) + { + VERIFY(e == 4 ); + test = true; + } + + VERIFY( test ); +} + +void test07() +{ + bool test = false; + + std::promise<void> p1; + std::future<void> f1 = p1.get_future(); + + p1.set_value(); + + try + { + p1.set_value(); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + f1.get(); + VERIFY( test ); +} + +void test08() +{ + bool test = false; + + std::promise<void> p1; + std::future<void> f1 = p1.get_future(); + + p1.set_value(); + + try + { + p1.set_exception(std::make_exception_ptr(4)); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + f1.get(); + VERIFY( test ); +} + +void test09() +{ + bool test = false; + + std::promise<void> p1; + std::future<void> f1 = p1.get_future(); + + p1.set_exception(std::make_exception_ptr(4)); + + try + { + p1.set_value(); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == + std::make_error_code(std::future_errc::promise_already_satisfied)); + test = true; + } + + std::chrono::milliseconds delay(1); + VERIFY( f1.wait_for(delay) == std::future_status::ready ); + test = false; + try + { + f1.get(); + VERIFY( false ); + } + catch (int e) + { + VERIFY(e == 4 ); + test = true; + } + + VERIFY( test ); +} + +// Check for no_state error condition (PR libstdc++/80316) + +void test10() +{ + using namespace std; + + promise<int> p1; + promise<int> p2(std::move(p1)); + try + { + p1.set_value(1); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == make_error_code(future_errc::no_state)); + } +} + +void test11() +{ + using namespace std; + + promise<int&> p1; + promise<int&> p2(std::move(p1)); + try + { + int i = 0; + p1.set_value(i); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == make_error_code(future_errc::no_state)); + } +} + +void test12() +{ + using namespace std; + + promise<void> p1; + promise<void> p2(std::move(p1)); + try + { + p1.set_value(); + VERIFY( false ); + } + catch (std::future_error& e) + { + VERIFY(e.code() == make_error_code(future_errc::no_state)); + } +} + int main() { test01(); test02(); + test03(); + test04(); + test05(); + test06(); + test07(); + test08(); + test09(); + test10(); + test11(); + test12(); return 0; }