https://gcc.gnu.org/bugzilla/show_bug.cgi?id=110542

            Bug ID: 110542
           Summary: use of allocated storage after deallocation in a
                    constant expression: std::array of std::vector<bool>
           Product: gcc
           Version: 13.1.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: libstdc++
          Assignee: unassigned at gcc dot gnu.org
          Reporter: hal.finkel.oss at gmail dot com
  Target Milestone: ---

For this test case:

```
#include <vector>
#include <array>

struct s_t {
  std::vector<std::array<std::vector<int>, 1>> v;

  constexpr void rz(std::size_t n) {
    v.resize(n);
    for (auto &x : v) for (auto &w : x) w.resize(n);
  }
};

constexpr std::size_t test1() {
  s_t s;
  s.rz(1);
  s.rz(2);
  return 1;
}

int main() {
  static_assert(test1() == 1);
}
```

g++ -std=c++20 13.1.0 (and GCC trunk as of today on https://gcc.godbolt.org/):
ERROR:

```
tc1.cpp: In function ‘int main()’:
tc1.cpp:21:25: error: non-constant condition for static assertion
   21 |   static_assert(test1() == 1);
      |                 ~~~~~~~~^~~~
tc1.cpp:21:22:   in ‘constexpr’ expansion of ‘test1()’
tc1.cpp:16:7:   in ‘constexpr’ expansion of ‘s.s_t::rz(2)’
tc1.cpp:9:49:   in ‘constexpr’ expansion of ‘(&
w)->std::vector<int>::resize(n)’
/gcc-13.1.0/include/c++/13/bits/stl_vector.h:1011:21:   in ‘constexpr’
expansion of
‘((std::vector<int>*)this)->std::vector<int>::_M_default_append((__new_size -
((std::vector<int>*)this)->std::vector<int>::size()))’
/gcc-13.1.0/include/c++/13/bits/vector.tcc:676:16:   in ‘constexpr’ expansion
of ‘std::vector<int>::_S_relocate(__old_start, __old_finish, __new_start, (*
&((std::vector<int>*)this)->std::vector<int>::<anonymous>.std::_Vector_base<int,
std::allocator<int> >::_M_get_Tp_allocator()))’
/gcc-13.1.0/include/c++/13/bits/stl_vector.h:504:26:   in ‘constexpr’ expansion
of ‘std::__relocate_a<int*, int*, allocator<int> >(__first, __last, __result,
(* & __alloc))’
/gcc-13.1.0/include/c++/13/bits/stl_uninitialized.h:1142:33:   in ‘constexpr’
expansion of ‘std::__relocate_a_1<int, int>(std::__niter_base<int*>(__first),
std::__niter_base<int*>(__last), std::__niter_base<int*>(__result), (* &
__alloc))’
/gcc-13.1.0/include/c++/13/bits/stl_uninitialized.h:1122:35:   in ‘constexpr’
expansion of ‘std::__relocate_a_1<int*, __gnu_cxx::__normal_iterator<int*,
void>, allocator<int> >(__first, __last, __out, (* & __alloc))’
/gcc-13.1.0/include/c++/13/bits/stl_uninitialized.h:1100:26:   in ‘constexpr’
expansion of ‘std::__relocate_object_a<int, int, allocator<int>
>(std::__addressof<int>((* & __cur.__gnu_cxx::__normal_iterator<int*,
void>::operator*())), std::__addressof<int>((* __first)), (* & __alloc))’
/gcc-13.1.0/include/c++/13/bits/stl_uninitialized.h:1072:26:   in ‘constexpr’
expansion of ‘std::allocator_traits<std::allocator<int> >::construct<int,
int>((* & __alloc), __dest, (* & std::move<int&>((*(int*)__orig))))’
/gcc-13.1.0/include/c++/13/bits/alloc_traits.h:539:21:   in ‘constexpr’
expansion of ‘std::construct_at<int, int>(__p, (* & std::forward<int>((* &
__args#0))))’
tc1.cpp:21:25: error: use of allocated storage after deallocation in a constant
expression
In file included from /gcc-13.1.0/include/c++/13/vector:63,
                 from tc1.cpp:1:
/gcc-13.1.0/include/c++/13/bits/allocator.h:195:52: note: allocated here
  195 |             return static_cast<_Tp*>(::operator new(__n));
      |                                      ~~~~~~~~~~~~~~^~~~~

```

clang -std=c++20 15, (and 16 and trunk as of today on https://gcc.godbolt.org/,
noting that these appear to use headers from libstdc++ gcc-12.2.0; below is
from clang 15 using GCC 13.1.0 headers): ERROR:

```
tc1.cpp:21:17: error: static assertion expression is not an integral constant
expression
  static_assert(test1() == 1);
                ^~~~~~~~~~~~
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_algobase.h:931:11:
note: assignment to object outside its lifetime is not allowed in a constant
expression
        *__first = __tmp;
                 ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_algobase.h:977:7:
note: in call to '__fill_a1(&{*new int[2]#4}[1], &{*new int[2]#4}[2], {*new
int[2]#4}[0])'
    { std::__fill_a1(__first, __last, __value); }
      ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_algobase.h:1128:7:
note: in call to '__fill_a(&{*new int[2]#4}[1], &{*new int[2]#4}[2], {*new
int[2]#4}[0])'
      std::__fill_a(__first, __first + __n, __value);
      ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_algobase.h:1157:14:
note: in call to '__fill_n_a(&{*new int[2]#4}[1], 1, {*new int[2]#4}[0],
{{{{}}}})'
      return std::__fill_n_a(__first, std::__size_to_integer(__n), __value,
             ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_uninitialized.h:668:18:
note: in call to 'fill_n(&{*new int[2]#4}[1], 1, {*new int[2]#4}[0])'
              __first = std::fill_n(__first, __n - 1, *__val);
                        ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_uninitialized.h:704:14:
note: in call to '__uninit_default_n(&{*new int[2]#4}[1], 2)'
      return __uninitialized_default_n_1<__is_trivial(_ValueType)
             ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_uninitialized.h:773:14:
note: (skipping 1 call in backtrace; use -fconstexpr-backtrace-limit=0 to see
all)
    { return std::__uninitialized_default_n(__first, __n); }
             ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/vector.tcc:668:9:
note: in call to '__uninitialized_default_n_a(&{*new int[2]#4}[0], 2, {*new
std::array<std::vector<int>, 1>[2]#2}[1]._M_elems[0]._Vector_base::_M_impl)'
                      std::__uninitialized_default_n_a(__new_start + __size,
                      ^
/gcc-13.1.0/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13/bits/stl_vector.h:1011:4:
note: in call to '&{*new std::array<std::vector<int>,
1>[2]#2}[1]._M_elems[0]->_M_default_append(2)'
          _M_default_append(__new_size - size());
          ^
tc1.cpp:9:43: note: in call to '&{*new std::array<std::vector<int>,
1>[2]#2}[1]._M_elems[0]->resize(2)'
    for (auto &x : v) for (auto &w : x) w.resize(n);
                                          ^
tc1.cpp:16:5: note: in call to '&s->rz(2)'
  s.rz(2);
    ^
tc1.cpp:21:17: note: in call to 'test1()'
  static_assert(test1() == 1);
                ^
1 error generated.

```

clang -std=c++20 -stdlib=libc++ 15, 16, and trunk as of today on
https://gcc.godbolt.org/): OK

I realize that this might be two compiler bugs, and I see, e.g., GCC bug
101777, but the fact that both GCC and clang provide what seem like they might
be the same semantic error, a use after deallocation, albeit pointing at
slightly different places in the libstdc++ headers, both on the second resize,
and it works with libc++, is why I suspect that it might be an issue in the
library (and the error message here is different from the one in 101777). Or,
of course, my code has UB and the compilers are correct.

Reply via email to