This attempts to solve some of the problems when mixing std::valarray operations and 'auto', by storing nested closure objects as values instead of references. This means we don't end up with dangling references to temporary closures that have already been destroyed.
This makes the closure objects introduced by the library less error-prone, but it's still possible to write incorrect code by using temporary valarrays, e.g. std::valarray<int> f(); auto x = f() * 2; std::valarray<int> y = x; Here the closure object 'x' has a dangling reference to the temporary returned by f(). It might be possible to do something about this by overloading the operators for valarray rvalues and moving the valarray into the closure, instead of holding a const-reference. I don't plan to work on that. Performance seems to be unaffected by this patch, although I didn't test that very thoroughly. Strictly speaking this is an ABI change as it changes the size and layout of the closure types like _BinClos etc. so that their _M_expr member is at least two words, not just one for a reference. Those closure types should never appear in API boundaries or as class members (if anybody was doing that by using 'auto' it wouldn't have worked correctly anyway) so I think we can just change them, without renaming the types or moving them into an inline namespace so they mangle differently. Does anybody disagree? The PR is marked as a regression because the example in the PR used to "work" with older releases. That's probably only because they didn't optimize as aggressively and so the stack space of the expired temporaries wasn't reused (ASan still shows the bug was there in older releases even if it doesn't crash). As a regression this should be backported, but the layout changes make me pause a little when considering making the change on stable release branches. PR libstdc++/83860 * include/bits/gslice_array.h (gslice_array): Define default constructor as deleted, as per C++11 standard. * include/bits/mask_array.h (mask_array): Likewise. * include/bits/slice_array.h (slice_array): Likewise. * include/bits/valarray_after.h (_GBase::_M_expr, _IBase::_M_expr): Use _ValArrayRef for type of data members. * include/bits/valarray_before.h (_ValArrayRef): New helper for type of data members in closure objects. (_FunBase::_M_expr, _UnBase::_M_expr, _BinBase::_M_expr1) (_BinBase::_M_expr2, _BinBase2::_M_expr1, _BinBase1::_M_expr2) (_SBase::_M_expr): Use _ValArrayRef for type of data members. * testsuite/26_numerics/valarray/83860.cc: New. I'll commit this to trunk only for now, unless anybody sees a problem with the approach, or thinks the layout changes require new mangled names for the closures.
commit 1dd9f0f54457eb2c44c6b1125800d94eaac0cb2f Author: Jonathan Wakely <jwak...@redhat.com> Date: Fri Apr 6 01:30:19 2018 +0100 PR libstdc++/83860 avoid dangling references in valarray closure types PR libstdc++/83860 * include/bits/gslice_array.h (gslice_array): Define default constructor as deleted, as per C++11 standard. * include/bits/mask_array.h (mask_array): Likewise. * include/bits/slice_array.h (slice_array): Likewise. * include/bits/valarray_after.h (_GBase::_M_expr, _IBase::_M_expr): Use _ValArrayRef for type of data members. * include/bits/valarray_before.h (_ValArrayRef): New helper for type of data members in closure objects. (_FunBase::_M_expr, _UnBase::_M_expr, _BinBase::_M_expr1) (_BinBase::_M_expr2, _BinBase2::_M_expr1, _BinBase1::_M_expr2) (_SBase::_M_expr): Use _ValArrayRef for type of data members. * testsuite/26_numerics/valarray/83860.cc: New. diff --git a/libstdc++-v3/include/bits/gslice_array.h b/libstdc++-v3/include/bits/gslice_array.h index 2da7e0442bb..715c53bd616 100644 --- a/libstdc++-v3/include/bits/gslice_array.h +++ b/libstdc++-v3/include/bits/gslice_array.h @@ -128,8 +128,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION gslice_array(_Array<_Tp>, const valarray<size_t>&); +#if __cplusplus < 201103L // not implemented gslice_array(); +#else + public: + gslice_array() = delete; +#endif }; template<typename _Tp> diff --git a/libstdc++-v3/include/bits/mask_array.h b/libstdc++-v3/include/bits/mask_array.h index 84671cb43d6..c11691a243a 100644 --- a/libstdc++-v3/include/bits/mask_array.h +++ b/libstdc++-v3/include/bits/mask_array.h @@ -131,8 +131,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION const _Array<bool> _M_mask; const _Array<_Tp> _M_array; +#if __cplusplus < 201103L // not implemented mask_array(); +#else + public: + mask_array() = delete; +#endif }; template<typename _Tp> diff --git a/libstdc++-v3/include/bits/slice_array.h b/libstdc++-v3/include/bits/slice_array.h index 05b096bb5a9..b025373180f 100644 --- a/libstdc++-v3/include/bits/slice_array.h +++ b/libstdc++-v3/include/bits/slice_array.h @@ -192,8 +192,13 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION const size_t _M_stride; const _Array<_Tp> _M_array; +#if __cplusplus < 201103L // not implemented slice_array(); +#else + public: + slice_array() = delete; +#endif }; template<typename _Tp> diff --git a/libstdc++-v3/include/bits/valarray_after.h b/libstdc++-v3/include/bits/valarray_after.h index 7f62b292ed5..3cfd6a510d7 100644 --- a/libstdc++-v3/include/bits/valarray_after.h +++ b/libstdc++-v3/include/bits/valarray_after.h @@ -59,8 +59,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return _M_index.size(); } private: - const _Dom& _M_expr; - const valarray<size_t>& _M_index; + typename _ValArrayRef<_Dom>::__type _M_expr; + const valarray<size_t>& _M_index; }; template<typename _Tp> @@ -128,8 +128,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return _M_index.size(); } private: - const _Dom& _M_expr; - const valarray<size_t>& _M_index; + typename _ValArrayRef<_Dom>::__type _M_expr; + const valarray<size_t>& _M_index; }; template<class _Dom> diff --git a/libstdc++-v3/include/bits/valarray_before.h b/libstdc++-v3/include/bits/valarray_before.h index a77fdf203b2..73f0b4f61e5 100644 --- a/libstdc++-v3/include/bits/valarray_before.h +++ b/libstdc++-v3/include/bits/valarray_before.h @@ -406,6 +406,18 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION typedef bool result_type; }; + // Closure types already have reference semantics and are often short-lived, + // so store them by value to avoid (some cases of) dangling references to + // out-of-scope temporaries. + template<typename _Tp> + struct _ValArrayRef + { typedef const _Tp __type; }; + + // Use real references for std::valarray objects. + template<typename _Tp> + struct _ValArrayRef< valarray<_Tp> > + { typedef const valarray<_Tp>& __type; }; + // // Apply function taking a value/const reference closure // @@ -425,7 +437,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION size_t size() const { return _M_expr.size ();} private: - const _Dom& _M_expr; + typename _ValArrayRef<_Dom>::__type _M_expr; value_type (*_M_func)(_Arg); }; @@ -490,7 +502,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION size_t size() const { return _M_expr.size(); } private: - const _Arg& _M_expr; + typename _ValArrayRef<_Arg>::__type _M_expr; }; template<class _Oper, class _Dom> @@ -536,8 +548,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION size_t size() const { return _M_expr1.size(); } private: - const _FirstArg& _M_expr1; - const _SecondArg& _M_expr2; + typename _ValArrayRef<_FirstArg>::__type _M_expr1; + typename _ValArrayRef<_SecondArg>::__type _M_expr2; }; @@ -557,8 +569,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION size_t size() const { return _M_expr1.size(); } private: - const _Clos& _M_expr1; - const _Vt& _M_expr2; + typename _ValArrayRef<_Clos>::__type _M_expr1; + _Vt _M_expr2; }; template<class _Oper, class _Clos> @@ -577,8 +589,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION size_t size() const { return _M_expr2.size(); } private: - const _Vt& _M_expr1; - const _Clos& _M_expr2; + _Vt _M_expr1; + typename _ValArrayRef<_Clos>::__type _M_expr2; }; template<class _Oper, class _Dom1, class _Dom2> @@ -592,7 +604,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; template<class _Oper, typename _Tp> - struct _BinClos<_Oper,_ValArray, _ValArray, _Tp, _Tp> + struct _BinClos<_Oper, _ValArray, _ValArray, _Tp, _Tp> : _BinBase<_Oper, valarray<_Tp>, valarray<_Tp> > { typedef _BinBase<_Oper, valarray<_Tp>, valarray<_Tp> > _Base; @@ -671,7 +683,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION // // slice_array closure. // - template<typename _Dom> + template<typename _Dom> class _SBase { public: @@ -689,7 +701,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION { return _M_slice.size (); } private: - const _Dom& _M_expr; + typename _ValArrayRef<_Dom>::__type _M_expr; const slice& _M_slice; }; diff --git a/libstdc++-v3/testsuite/26_numerics/valarray/83860.cc b/libstdc++-v3/testsuite/26_numerics/valarray/83860.cc new file mode 100644 index 00000000000..a9599798b41 --- /dev/null +++ b/libstdc++-v3/testsuite/26_numerics/valarray/83860.cc @@ -0,0 +1,105 @@ +// Copyright (C) 2018 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/>. + +// { dg-do run { target c++11 } } + +#include <valarray> +#include <testsuite_hooks.h> + +const std::valarray<int> v{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 +}; + +bool +all_of(const std::valarray<bool>& vals) +{ + for (bool b : vals) + if (!b) + return false; + return true; +} + +void +test01() +{ + // PR libstdc++/83860 + auto sum = v + v + v; + std::valarray<int> vsum = sum; + VERIFY( all_of( vsum == (v + v + v) ) ); +} + +void +test02() +{ + auto neg = -(-v); + std::valarray<int> vneg = neg; + VERIFY( all_of( vneg == v ) ); +} + +void +test03() +{ + auto diff = v + -v; + std::valarray<int> vdiff = diff; + VERIFY( all_of( vdiff == (v - v) ) ); +} + +void +test04() +{ + auto sum = -v + -v; + std::valarray<int> vsum = sum; + VERIFY( all_of( vsum == (-v + -v) ) ); +} + +void +test05() +{ + auto sum = -(-v + -v); + std::valarray<int> vsum = sum; + VERIFY( all_of( vsum == (v + v) ) ); +} + +void +test06() +{ + auto prod = 3 * +v * 2; + std::valarray<int> vprod = prod; + VERIFY( all_of( vprod == (6 * v) ) ); +} + +void +test07() +{ + auto valfun = [](int i) { return i; }; + auto reffun = [](const int& i) { return i; }; + auto sum = (v.apply(valfun) + v.apply(reffun)); + std::valarray<int> vsum = sum; + VERIFY( all_of( vsum == (v + v) ) ); +} + +int +main() +{ + test01(); + test02(); + test03(); + test04(); + test05(); + test06(); + test07(); +}