Skip to content

-Wfree-nonheap-object false positive in std::variant destructor chain when variant contains std::vector<int> #20

@morinim

Description

@morinim

When compiling a project with GCC 15.2.0, I receive the following diagnostic:

[33/231] Building CXX object kernel/CMakeFiles/ultra.dir/gp/individual.cc.o
In file included from /opt/gcc-15.2.0/include/c++/15.2.0/x86_64-slackware-linux/bits/c++allocator.h:33,
from /opt/gcc-15.2.0/include/c++/15.2.0/bits/allocator.h:46,
from /opt/gcc-15.2.0/include/c++/15.2.0/bits/alloc_traits.h:39,
from /opt/gcc-15.2.0/include/c++/15.2.0/ext/alloc_traits.h:36,
from /opt/gcc-15.2.0/include/c++/15.2.0/bits/hashtable_policy.h:39,
from /opt/gcc-15.2.0/include/c++/15.2.0/bits/hashtable.h:37,
from /opt/gcc-15.2.0/include/c++/15.2.0/bits/unordered_map.h:33,
from /opt/gcc-15.2.0/include/c++/15.2.0/unordered_map:43,
from /opt/gcc-15.2.0/include/c++/15.2.0/functional:65,
from /home/../prj/ultra/src/kernel/gp/individual.cc:13:
In member function ‘void std::__new_allocator<_Tp>::deallocate(_Tp*, size_type) [with _Tp = int]’,
inlined from ‘constexpr void std::allocator< >::deallocate(_Tp*, std::size_t) [with _Tp = int]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/allocator.h:215:35,
inlined from ‘static constexpr void std::allocator_traits<std::allocator<_Up> >::deallocate(allocator_type&, pointer, size_type) [with _Tp = int]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/alloc_traits.h:649:23,
inlined from ‘constexpr void std::_Vector_base<_Tp, _Alloc>::_M_deallocate(pointer, std::size_t) [with _Tp = int; _Alloc = std::allocator]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/stl_vector.h:396:19,
inlined from ‘constexpr void std::_Vector_base<_Tp, _Alloc>::_M_deallocate(pointer, std::size_t) [with _Tp = int; _Alloc = std::allocator]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/stl_vector.h:392:7,
inlined from ‘constexpr std::_Vector_base<_Tp, _Alloc>::_Vector_base() [with _Tp = int; _Alloc = std::allocator]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/stl_vector.h:375:15,
inlined from ‘constexpr std::vector<_Tp, _Alloc>::vector() [with _Tp = int; _Alloc = std::allocator]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/stl_vector.h:805:7,
inlined from ‘constexpr void std::destroy_at(_Tp*) [with _Tp = vector]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/stl_construct.h:88:18,
inlined from ‘constexpr void std::_Destroy(_Tp*) [with _Tp = vector]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/stl_construct.h:164:22,
inlined from ‘std::__detail::__variant::_Variant_storage<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::_M_reset()::<lambda(auto:11&&)> mutable [with auto:11 = std::vector&]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:493:19,
inlined from ‘constexpr _Res std::__invoke_impl(__invoke_other, _Fn&&, _Args&& ...) [with _Res = void; _Fn = __detail::__variant::_Variant_storage<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::_M_reset()::<lambda(auto:11&&)>; _Args = {vector<int, allocator >&}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/invoke.h:63:36,
inlined from ‘constexpr std::enable_if_t<((bool)is_invocable_r_v<_Res, _Callable, _Args ...>), _Res> std::__invoke_r(_Callable&&, _Args&& ...) [with _Res = void; _Callable = __detail::__variant::_Variant_storage<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::_M_reset()::<lambda(auto:11&&)>; _Args = {vector<int, allocator >&}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/invoke.h:113:28,
inlined from ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...)>, std::integer_sequence<long unsigned int, __indices ...> >::__visit_invoke(_Visitor&&, _Variants ...) [with _Result_type = void; _Visitor = std::__detail::__variant::_Variant_storage<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::_M_reset()::<lambda(auto:11&&)>&&; _Variants = {std::variant<std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&}; long unsigned int ...__indices = {7}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:1059:40,
inlined from ‘constexpr decltype(auto) std::__do_visit(_Visitor&&, _Variants&& ...) [with _Result_type = void; _Visitor = __detail::__variant::_Variant_storage<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::_M_reset()::<lambda(auto:11&&)>; _Variants = {variant<monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >&}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:1898:5,
inlined from ‘constexpr void std::__detail::__variant::_Variant_storage<false, _Types ...>::_M_reset() [with _Types = {std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator >}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:491:23,
inlined from ‘constexpr std::__detail::__variant::_Variant_storage<false, _Types ...>::
_Variant_storage() [with _Types = {std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator >}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:501:17,
inlined from ‘constexpr std::__detail::__variant::_Copy_ctor_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::
_Copy_ctor_base()’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:576:12,
inlined from ‘constexpr std::__detail::__variant::_Move_ctor_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::_Move_ctor_base()’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:613:12,
inlined from ‘constexpr std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::
_Copy_assign_base()’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:651:12,
inlined from ‘constexpr std::__detail::__variant::_Move_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::_Move_assign_base()’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:703:12,
inlined from ‘constexpr std::__detail::__variant::_Variant_base<std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::
_Variant_base()’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:757:12,
inlined from ‘constexpr std::variant<_Types>::~variant() [with _Types = {std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator >}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:1490:28,
inlined from ‘std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::operator=(const std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&)::<lambda(auto:16&&, auto:17)> mutable [with auto:16 = const std::__cxx11::basic_string&; auto:17 = std::integral_constant<long unsigned int, 3>]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:679:16,
inlined from ‘constexpr _Res std::__invoke_impl(__invoke_other, _Fn&&, _Args&& ...) [with _Res = void; _Fn = __detail::__variant::_Copy_assign_base<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::operator=(const std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&)::<lambda(auto:16&&, auto:17)>; _Args = {const __cxx11::basic_string<char, char_traits, allocator >&, integral_constant<long unsigned int, 3>}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/invoke.h:63:36,
inlined from ‘constexpr typename std::__invoke_result<_Functor, _ArgTypes>::type std::__invoke(_Callable&&, _Args&& ...) [with _Callable = __detail::__variant::_Copy_assign_base<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::operator=(const std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&)::<lambda(auto:16&&, auto:17)>; _Args = {const __cxx11::basic_string<char, char_traits, allocator >&, integral_constant<long unsigned int, 3>}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/bits/invoke.h:98:40,
inlined from ‘static constexpr decltype(auto) std::__detail::__variant::__gen_vtable_impl<std::__detail::__variant::_Multi_array<_Result_type ()(_Visitor, _Variants ...)>, std::integer_sequence<long unsigned int, __indices ...> >::__visit_invoke(_Visitor&&, _Variants ...) [with _Result_type = std::__detail::__variant::__variant_idx_cookie; _Visitor = std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >::operator=(const std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&)::<lambda(auto:16&&, auto:17)>&&; _Variants = {const std::variant<std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&}; long unsigned int ...__indices = {3}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:1044:17,
inlined from ‘constexpr decltype(auto) std::__do_visit(_Visitor&&, _Variants&& ...) [with _Result_type = __detail::__variant::__variant_idx_cookie; _Visitor = __detail::__variant::_Copy_assign_base<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::operator=(const std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&)::<lambda(auto:16&&, auto:17)>; _Variants = {const variant<monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >&}]’ at /opt/gcc-15.2.0/include/c++/15.2.0/variant:1894:5:
/opt/gcc-15.2.0/include/c++/15.2.0/bits/new_allocator.h:172:66: warning: ‘void operator delete(void*, std::size_t)’ called on unallocated object ‘’ [-Wfree-nonheap-object]
172 | _GLIBCXX_OPERATOR_DELETE(_GLIBCXX_SIZED_DEALLOC(__p, __n));
| ^
In file included from /home/../prj/ultra/src/kernel/value.h:18,
from /home/../prj/ultra/src/kernel/symbol.h:19,
from /home/../prj/ultra/src/kernel/terminal.h:16,
from /home/../prj/ultra/src/kernel/symbol_set.h:16,
from /home/../prj/ultra/src/kernel/individual.h:19,
from /home/../prj/ultra/src/kernel/gp/individual.h:18,
from /home/../prj/ultra/src/kernel/gp/individual.cc:15:
/opt/gcc-15.2.0/include/c++/15.2.0/variant: In function ‘constexpr decltype(auto) std::__do_visit(_Visitor&&, _Variants&& ...) [with _Result_type = __detail::__variant::__variant_idx_cookie; _Visitor = __detail::__variant::_Copy_assign_base<false, monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >::operator=(const std::__detail::__variant::_Copy_assign_base<false, std::monostate, int, double, std::__cxx11::basic_string<char, std::char_traits, std::allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, std::vector<int, std::allocator > >&)::<lambda(auto:16&&, auto:17)>; _Variants = {const variant<monostate, int, double, __cxx11::basic_string<char, char_traits, allocator >, const ultra::nullary*, ultra::param_address, const ultra::src::variable*, vector<int, allocator > >&}]’:
/opt/gcc-15.2.0/include/c++/15.2.0/variant:679:30: note: declared here
679 | __self = _Variant(in_place_index<__j>, __rhs_mem);

The warning is triggered inside libstdc++, specifically during destruction or assignment of a std::variant that includes a std::vector<int> alternative.

The full inlining trace points to:

  • std::vector<int>::~vector();
  • called from std::__detail::__variant::_Variant_storage::_M_reset();
  • during destruction and copy-assignment of std::variant<...>.

This happens even though the code never frees memory manually, and Clang as well as GCC ≤ 13 compile the same sources without any warnings.

The problematic variant structure looks like:

using value_t = std::variant<
    std::monostate,
    int,
    double,
    std::string,
    const nullary*,
    param_address,
    const src::variable*,
    std::vector<int>
>;

The warning appears whenever value_t is destroyed or assigned (e.g. in constructors/assignment operators of surrounding classes).

The backtrace shows the warning originates entirely from inlined libstdc++ destructors, not from user code.

The problem occurs only when the variant contains a std::vector<T> alternative; removing it makes the warning disappear.

It appears to be a misanalysis in the inliner or the data-flow engine for constexpr destructor paths in the variant implementation.

This looks very similar to regressions reported starting with GCC 14, where certain std::variant destructor paths produce false positives.

I believe this is a GCC bug, not a library or user issue.


For the moment, the only reliable workaround is to locally disable the warning around the inclusion of the file using the variant:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
#include "value.h"
#pragma GCC diagnostic pop

Suppressing the warning globally is undesirable.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions