On Mon, 8 Sept 2025 at 14:38, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > On Mon, Sep 8, 2025 at 2:45 PM Jonathan Wakely <jwak...@redhat.com> wrote: >> >> This adds checks when incrementing the shared count and weak count and >> will trap if they would be >> be incremented past its maximum. The maximum value is the value at which >> incrementing it produces an invalid use_count(). So that is either the >> maximum positive value of _Atomic_word, or for targets where we now >> allow the counters to wrap around to negative values, the "maximum" >> value is -1, because that is the value at which one more increment >> overflows the usable range and resets the counter to zero. >> >> libstdc++-v3/ChangeLog: >> >> PR libstdc++/71945 >> * include/bits/shared_ptr_base.h (_Sp_counted_base::_S_chk): >> Trap if a reference count cannot be incremented any higher. >> (_Sp_counted_base::_M_add_ref_copy): Use _S_chk. >> (_Sp_counted_base::_M_add_weak_ref): Likewise. >> (_Sp_counted_base<_S_mutex>::_M_add_ref_lock_nothrow): Likewise. >> (_Sp_counted_base<_S_atomic>::_M_add_ref_lock_nothrow): Likewise. >> (_Sp_counted_base<_S_single>::_M_add_ref_copy): Use _S_chk. >> --- >> >> v2: The original patch series only had two patches, but the >> implementation of trapping on counter overflow has been split out from >> PATCH 2/2 into a third patch. > > If the use of max in _M_weak_add_ref is simple type, then this is OK for trunk > with the change. Otherwise, I think I do not understand the patches. >> >> >> We should decide whether we want to do these checks and traps >> unconditionally, or only for _GLIBCXX_ASSERTIONS. >> >> Tested powerpc64le-linux. >> >> libstdc++-v3/include/bits/shared_ptr_base.h | 51 +++++++++++++++++++-- >> 1 file changed, 47 insertions(+), 4 deletions(-) >> >> diff --git a/libstdc++-v3/include/bits/shared_ptr_base.h >> b/libstdc++-v3/include/bits/shared_ptr_base.h >> index 2ea53ae15998..a636e3e30ba5 100644 >> --- a/libstdc++-v3/include/bits/shared_ptr_base.h >> +++ b/libstdc++-v3/include/bits/shared_ptr_base.h >> @@ -148,7 +148,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> // Increment the use count (used when the count is greater than zero). >> void >> _M_add_ref_copy() >> - { __gnu_cxx::__atomic_add_dispatch(&_M_use_count, 1); } >> + { _S_chk(__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, 1)); } >> >> // Increment the use count if it is non-zero, throw otherwise. >> void >> @@ -200,7 +200,15 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> // Increment the weak count. >> void >> _M_weak_add_ref() noexcept >> - { __gnu_cxx::__atomic_add_dispatch(&_M_weak_count, 1); } >> + { >> + // _M_weak_count can always use negative values because it cannot be >> + // observed by users (unlike _M_use_count). See _S_chk for details. >> + constexpr _Atomic_word __max >> + = __gnu_cxx::__int_traits<_Atomic_word>::__max; > > Should this be -1, the comment says we can always use negative values for > _M_weak_count.
Yes, just a brain-failure. The v1 patch had: + // _M_weak_count can always use negative values (except for _S_single) + // because only _M_use_count can be observed. See _M_chk for details. + constexpr _Atomic_word __max = _Lp != _S_single + ? -1 : __gnu_cxx::__int_traits<_Atomic_word>::__max; and when I made _S_single consistent in the v2 patch I must have deleted the wrong branch of the conditional-expression. Doh. >> >> + >> + if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, 1) == >> __max) >> + [[__unlikely__]] __builtin_trap(); >> + } >> >> // Decrement the weak count. >> void >> @@ -238,6 +246,35 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> _Sp_counted_base(_Sp_counted_base const&) = delete; >> _Sp_counted_base& operator=(_Sp_counted_base const&) = delete; >> >> + // Called when incrementing _M_use_count to cause a trap on overflow. >> + // This should be passed the value of the counter before the >> increment. >> + static void >> + _S_chk(_Atomic_word __count) >> + { >> + // __max is the maximum allowed value for the shared reference count. >> + // All valid reference count values need to fit into [0,LONG_MAX) >> + // because users can observe the count via shared_ptr::use_count(). >> + // >> + // When long is wider than _Atomic_word, _M_use_count can go negative >> + // and the cast in _Sp_counted_base::use_count() will turn it into a >> + // positive value suitable for returning to users. The implementation >> + // only cares whether _M_use_count reaches zero after a decrement, >> + // so negative values are not a problem internally. >> + // So when possible, use -1 for __max (incrementing past that would >> + // overflow _M_use_count to 0, which means an empty shared_ptr). >> + // >> + // When long is not wider than _Atomic_word, __max is just the type's >> + // maximum positive value. We cannot use negative counts because they >> + // would not fit in [0,LONG_MAX) after casting to an unsigned type, >> + // which would cause use_count() to return bogus values. >> + constexpr _Atomic_word __max >> + = sizeof(long) > sizeof(_Atomic_word) >> + ? -1 : __gnu_cxx::__int_traits<_Atomic_word>::__max; >> + >> + if (__count == __max) [[__unlikely__]] >> + __builtin_trap(); >> + } >> + >> _Atomic_word _M_use_count; // #shared >> _Atomic_word _M_weak_count; // #weak + (#shared != 0) >> }; >> @@ -248,7 +285,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> template<> >> inline void >> _Sp_counted_base<_S_single>::_M_add_ref_copy() >> - { __gnu_cxx::__atomic_add_single(&_M_use_count, 1); } >> + { >> + _S_chk(_M_use_count); >> + __gnu_cxx::__atomic_add_single(&_M_use_count, 1); >> + } >> >> template<> >> inline void >> @@ -284,7 +324,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> _M_add_ref_lock_nothrow() noexcept >> { >> __gnu_cxx::__scoped_lock sentry(*this); >> - if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, 1) == 0) >> + if (auto __c = __gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, >> 1)) >> + _S_chk(__c); >> + else >> { >> // Count was zero, so we cannot lock it to get a shared_ptr. >> // Reset to zero. This isn't racy, because there are no shared_ptr >> @@ -314,6 +356,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION >> while (!__atomic_compare_exchange_n(&_M_use_count, &__count, __count >> + 1, >> true, __ATOMIC_ACQ_REL, >> __ATOMIC_RELAXED)); >> + _S_chk(__count); >> return true; >> } >> >> -- >> 2.51.0 >>