[PATCH] libstdc++: Reduce chances of object aliasing for function wrapper.
Patrick Palka
ppalka@redhat.com
Tue Aug 26 14:35:12 GMT 2025
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
>
>
More information about the Libstdc++
mailing list