On Tue, 19 Aug 2025, Tomasz Kamiński wrote:

> Previously, an empty functor (EmptyIdFunc) stored inside a
> std::move_only_function being first member of a Composite class could have the
> same address as the base of the EmptyIdFunc type (see included test cases),
> resulting in two objects of the same type at the same address.
> 
> This commit addresses the issue by moving the internal buffer from the start
> of the wrapper object to a position after the manager function pointer. This
> minimizes aliasing with the stored buffer but doesn't completely eliminate it,
> especially when multiple empty base objects are involved (PR121180).
> 
> To facilitate this member reordering, the private section of _Mo_base was
> eliminated, and the corresponding _M_manager and _M_destroy members were made
> protected. They remain inaccessible to users, as user-facing wrappers derive
> from _Mo_base privately.

LGTM

> 
> libstdc++-v3/ChangeLog:
> 
>       * include/bits/funcwrap.h (__polyfunc::_Mo_base): Reorder _M_manage
>       and _M_storage members. Make _M_destroy protected and remove friend
>       declaration.
>       * testsuite/20_util/copyable_function/call.cc: Add test for aliasing
>       base class.
>       * testsuite/20_util/move_only_function/call.cc: Likewise.
> ---
> Testing on x86_64-linux locally. OK for trunk?
> 
>  libstdc++-v3/include/bits/funcwrap.h          | 14 ++++-------
>  .../20_util/copyable_function/call.cc         | 23 +++++++++++++++++++
>  .../20_util/move_only_function/call.cc        | 23 +++++++++++++++++++
>  3 files changed, 50 insertions(+), 10 deletions(-)
> 
> diff --git a/libstdc++-v3/include/bits/funcwrap.h 
> b/libstdc++-v3/include/bits/funcwrap.h
> index 9db4ab7811a..70ecfd93e36 100644
> --- a/libstdc++-v3/include/bits/funcwrap.h
> +++ b/libstdc++-v3/include/bits/funcwrap.h
> @@ -419,6 +419,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         _M_manage = _Manager::_S_empty;
>       }
>  
> +     void _M_destroy() noexcept
> +     { _M_manage(_Manager::_Op::_Destroy, _M_storage, nullptr); }
> +
>       ~_Mo_base()
>       { _M_destroy(); }
>  
> @@ -434,17 +437,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>         std::swap(_M_manage, __x._M_manage);
>       }
>  
> -     _Storage _M_storage;
> -
> -   private:
> -     void _M_destroy() noexcept
> -     { _M_manage(_Manager::_Op::_Destroy, _M_storage, nullptr); }
> -
>       _Manager::_Func _M_manage;
> -
> -#ifdef __glibcxx_copyable_function // C++ >= 26 && HOSTED
> -     friend class _Cpy_base;
> -#endif // __glibcxx_copyable_function
> +     _Storage _M_storage;
>     };
>  #endif // __glibcxx_copyable_function || __glibcxx_copyable_function
>  } // namespace __polyfunc
> diff --git a/libstdc++-v3/testsuite/20_util/copyable_function/call.cc 
> b/libstdc++-v3/testsuite/20_util/copyable_function/call.cc
> index 0ac5348f2fe..605422d5595 100644
> --- a/libstdc++-v3/testsuite/20_util/copyable_function/call.cc
> +++ b/libstdc++-v3/testsuite/20_util/copyable_function/call.cc
> @@ -214,6 +214,28 @@ test_params()
>    std::copyable_function<void(CompleteEnum)> f4;
>  }
>  
> +struct EmptyIdFunc
> +{
> +  EmptyIdFunc* operator()()
> +  { return this; }
> +};
> +
> +struct Composed : EmptyIdFunc
> +{
> +  std::move_only_function<EmptyIdFunc*()> nested;
> +};
> +
> +void
> +test_aliasing()
> +{
> +  Composed c;
> +  c.nested = EmptyIdFunc{};
> +
> +  EmptyIdFunc* baseAddr = c();
> +  EmptyIdFunc* nestedAddr = c.nested();
> +  VERIFY( baseAddr != nestedAddr );
> +};
> +
>  int main()
>  {
>    test01();
> @@ -222,4 +244,5 @@ int main()
>    test04();
>    test05();
>    test_params();
> +  test_aliasing();
>  }
> diff --git a/libstdc++-v3/testsuite/20_util/move_only_function/call.cc 
> b/libstdc++-v3/testsuite/20_util/move_only_function/call.cc
> index 72c8118d716..34ca73b4eb4 100644
> --- a/libstdc++-v3/testsuite/20_util/move_only_function/call.cc
> +++ b/libstdc++-v3/testsuite/20_util/move_only_function/call.cc
> @@ -214,6 +214,28 @@ test_params()
>    std::move_only_function<void(CompleteEnum)> f4;
>  }
>  
> +struct EmptyIdFunc
> +{
> +  EmptyIdFunc* operator()()
> +  { return this; }
> +};
> +
> +struct Composed : EmptyIdFunc
> +{
> +  std::move_only_function<EmptyIdFunc*()> nested;
> +};
> +
> +void
> +test_aliasing()
> +{
> +  Composed c;
> +  c.nested = EmptyIdFunc{};
> +
> +  EmptyIdFunc* baseAddr = c();
> +  EmptyIdFunc* nestedAddr = c.nested();
> +  VERIFY( baseAddr != nestedAddr );
> +};
> +
>  int main()
>  {
>    test01();
> @@ -222,4 +244,5 @@ int main()
>    test04();
>    test05();
>    test_params();
> +  test_aliasing();
>  }
> -- 
> 2.50.1
> 
> 

Reply via email to