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

            Bug ID: 127625
           Summary: Possible false positive -Warray-bounds in
                    std::vector::insert with a custom allocator at -O2
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: hello at davidmolnar dot dev
  Target Milestone: ---
              Host: x86_64-w64-mingw32
            Target: x86_64-w64-mingw32
             Build: x86_64-w64-mingw32

Created attachment 65693
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65693&action=edit
Preprocessed C++20 reproducer generated with GCC 16.2.0 (WinLibs/MinGW-w64)

I can reproduce a possible false positive -Warray-bounds diagnostic when
appending byte ranges to a std::vector using a custom allocator at -O2.

The standalone example fails with both tested Windows toolchains:
- GCC 16.2.0, WinLibs r1, MinGW-W64 x86_64-ucrt-posix-seh.
- GCC 15.2.0, WinLibs r7, MinGW-W64 x86_64-ucrt-posix-seh.

Host OS: Windows
Build, host and target: x86_64-w64-mingw32
The GCC 16.2.0 distribution uses MinGW-w64 14.0.0, UCRT and POSIX threads. The
complete compiler configuration and diagnostic are included below.

Save the following as repro.cpp. It uses only C++20 standard-library headers
and needs no external function definitions for this compile-only test.

#include <array>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <span>
#include <vector>

template <class T> struct Allocator {
    using value_type = T;
    Allocator() = default;
    template <class U> Allocator(const Allocator<U>&) {}
    T* allocate(std::size_t n) { return std::allocator<T>{}.allocate(n); }
    void deallocate(T* p, std::size_t n) { std::allocator<T>{}.deallocate(p,
n); }
    template <class U> bool operator==(const Allocator<U>&) const { return
true; }
};

struct Stream {
    std::vector<std::byte, Allocator<std::byte>> bytes;
    void write(std::span<const std::byte> src) {
        bytes.insert(bytes.end(), src.begin(), src.end());
    }
};

struct Record {
    std::uint8_t tag{};
    std::array<unsigned char, 4> id{};
    std::uint32_t index{};
    void serialize(Stream& s) const {
        s.write(std::as_bytes(std::span{&tag, 1}));
        s.write(std::as_bytes(std::span{id}));
        s.write(std::as_bytes(std::span{&index, 1}));
        const std::uint8_t prefix = 0;
        s.write(std::as_bytes(std::span{&prefix, 1}));
    }
};

void reproduce(const Record& record)
{
    Stream stream;
    record.serialize(stream);
}

Command:

g++ -std=c++20 -O2 -Werror=array-bounds -c repro.cpp

Expected: compilation without an array-bounds diagnostic.

Actual: compilation exits with code 1 and reports:

bits/stl_construct.h:133:7: error: 'void* __builtin_memcpy(void*, const void*,
long long unsigned int)' offset [5, 8] is out of the bounds [0, 5]
[-Werror=array-bounds=]

The diagnostic is emitted through std::vector::_M_range_insert and
Stream::write, inlined into the append of Record::index.

The source spans refer to live objects and do not overlap the destination
vector. The allocator delegates allocation and deallocation to std::allocator
without changing the requested size. The four appends contain 1, 4, 4 and 1
bytes on this target; vector::insert manages the destination capacity.

The same source also fails with GCC 16.2.0 using:

g++ -std=c++20 -O2 -Wall -Wextra -Werror -c repro.cpp

The attached preprocessed source was generated with:

g++ -v -save-temps=obj -std=c++20 -O2 -Werror=array-bounds -c repro.cpp -o
report-gcc16.o

Compiling the preprocessed file independently reproduces the same diagnostic
and exits with code 1:

g++ -std=c++20 -O2 -Werror=array-bounds -c report-gcc16.ii -o
report-gcc16-preprocessed.o

Additional checks on the same source with WinLibs GCC 15.2.0,
x86_64-w64-mingw32 (all use -std=c++20 -Werror=array-bounds unless stated
otherwise):

- -O2: the same diagnostic.
- -O0: compiles.
- -O2 -fno-inline: compiles.
- -O2 -fno-tree-vrp: the diagnostic remains.
- -O2 -fno-strict-aliasing -fwrapv -fno-aggressive-loop-optimizations: the
diagnostic remains.
- -O2 -D_GLIBCXX_ASSERTIONS: the diagnostic remains.
- -O2 -D_GLIBCXX_DEBUG: compiles.
- With -O2 -Wall -Wextra and without -Werror, compilation succeeds with the
corresponding warning.

With Ubuntu GCC 13.3.0 on x86_64 Linux, the source compiles with -std=c++20 -O2
-Wall -Wextra -Werror. This comparison changes both the compiler/library
version and the platform, so I have not established where a regression started.

A separate runtime smoke test calling the function with 10,000 valid records
passed with GCC 15.2.0 and libstdc++ assertions/debug mode, and with Ubuntu GCC
13.3.0 and ASan/UBSan. These checks do not establish the compiler's root cause
or prove the absence of all undefined behavior.

The example was reduced while investigating a similar diagnostic in Bitcoin
Core PR #35440:
https://github.com/bitcoin/bitcoin/pull/35440

The original PR also encountered the same diagnostic in CI with GCC 14.2 on
32-bit ARM and GCC 14.3 in the MinGW cross-compilation jobs (MSVCRT and UCRT).
I have not tested this standalone reproducer with those GCC 14 toolchains;
these are observations from the original PR's CI, not confirmed results for the
reduced example. This reproducer has no Bitcoin Core or third-party
dependencies.

I am reporting a suspected false-positive diagnostic, not a demonstrated
wrong-code bug. I found potentially related reports 107252, 117983 and 114945,
but have not established that this is a duplicate of any of them. PR117983
concerns -Wstringop-overflow and has a fix; its testcase differs from this one.

I have not tested a GCC development snapshot or a self-built upstream GCC. The
recorded standalone checks were performed on September 8-9, 2026.

Full GCC 16.2.0 verbose compiler output:

Using built-in specs.
COLLECT_GCC=D:\work\bitcoin-wallet\gcc-array-bounds-repro\toolchains\gcc16.2\mingw64\bin\g++.exe
OFFLOAD_TARGET_NAMES=nvptx-none
Target: x86_64-w64-mingw32
Configured with: ../configure
--prefix=/c/Prog/winlibs_staging_ucrt/home64/inst_gcc-16.2.0/share/gcc
--build=x86_64-w64-mingw32 --host=x86_64-w64-mingw32
--enable-offload-targets=nvptx-none --with-pkgversion='MinGW-W64
x86_64-ucrt-posix-seh, built by Brecht Sanders, r1' --with-tune=generic
--enable-checking=release --enable-threads=posix --disable-sjlj-exceptions
--disable-libunwind-exceptions --disable-serial-configure --disable-bootstrap
--enable-host-shared --enable-plugin --disable-default-ssp --disable-rpath
--disable-libstdcxx-debug --disable-version-specific-runtime-libs
--disable-symvers --enable-languages=c,c++,fortran,lto,objc,obj-c++
--disable-gold --disable-nls --disable-stage1-checking --disable-win32-registry
--disable-multilib --enable-ld --enable-libquadmath --enable-libada
--enable-libssp --enable-libstdcxx --enable-lto --enable-fully-dynamic-string
--enable-libgomp --enable-graphite --enable-mingw-wildcard
--enable-libstdcxx-time --enable-libstdcxx-pch
--with-mpc=/c/Prog/winlibs_staging_ucrt/custombuilt64
--with-mpfr=/c/Prog/winlibs_staging_ucrt/custombuilt64
--with-gmp=/c/Prog/winlibs_staging_ucrt/custombuilt64
--with-isl=/c/Prog/winlibs_staging_ucrt/custombuilt64
--disable-libstdcxx-backtrace --enable-install-libiberty --enable-__cxa_atexit
--without-included-gettext --with-diagnostics-color=auto
--enable-clocale=generic --enable-libgdiagnostics --with-libiconv
--with-system-zlib
--with-build-sysroot=/c/Prog/winlibs_staging_ucrt/home64/gcc-16.2.0/build_mingw/mingw-w64
CFLAGS='-I/c/Prog/winlibs_staging_ucrt/custombuilt64/include/libdl-win32  
-march=nocona -msahf -mtune=generic -O2 -Wno-error=format'
CXXFLAGS='-Wno-int-conversion  -march=nocona -msahf -mtune=generic -O2'
LDFLAGS='-pthread -Wl,--no-insert-timestamp -Wl,--dynamicbase
-Wl,--high-entropy-va -Wl,--nxcompat -Wl,--tsaware'
LD=/c/Prog/winlibs_staging_ucrt/custombuilt64/share/binutils/bin/ld.exe
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 16.2.0 (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders,
r1) 
COLLECT_GCC_OPTIONS='-v' '-save-temps=obj' '-std=c++20' '-O2'
'-Werror=array-bounds' '-c' '-o' 'report-gcc16.o' '-shared-libgcc'
'-mtune=generic' '-march=x86-64'

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/16.2.0/cc1plus.exe
-E -quiet -v -iprefix
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/
-D_REENTRANT repro.cpp -mtune=generic -march=x86-64 -std=c++20
-Werror=array-bounds -O2 -fpch-preprocess -o report-gcc16.ii
ignoring duplicate directory
"D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/lib/gcc/../../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include/c++/16.2.0"
ignoring duplicate directory
"D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/lib/gcc/../../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include/c++/16.2.0/x86_64-w64-mingw32"
ignoring duplicate directory
"D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/lib/gcc/../../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include/c++/16.2.0/backward"
ignoring duplicate directory
"D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/lib/gcc/../../lib/gcc/x86_64-w64-mingw32/16.2.0/include"
ignoring nonexistent directory
"C:/Prog/winlibs_staging_ucrt/home64/inst_gcc-16.2.0/share/gcc/include"
ignoring nonexistent directory
"/c/Prog/winlibs_staging_ucrt/home64/inst_gcc-16.2.0/share/gcc/include"
ignoring duplicate directory
"D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/lib/gcc/../../lib/gcc/x86_64-w64-mingw32/16.2.0/include-fixed"
ignoring duplicate directory
"D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/lib/gcc/../../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../x86_64-w64-mingw32/include"
ignoring nonexistent directory "/mingw/include"
#include "..." search starts here:
#include <...> search starts here:

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include/c++/16.2.0

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include/c++/16.2.0/x86_64-w64-mingw32

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include/c++/16.2.0/backward

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/include

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../include

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/include-fixed

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../lib/gcc/x86_64-w64-mingw32/16.2.0/../../../../x86_64-w64-mingw32/include
End of search list.
COLLECT_GCC_OPTIONS='-v' '-save-temps=obj' '-std=c++20' '-O2'
'-Werror=array-bounds' '-c' '-o' 'report-gcc16.o' '-shared-libgcc'
'-mtune=generic' '-march=x86-64'

D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/bin/../libexec/gcc/x86_64-w64-mingw32/16.2.0/cc1plus.exe
-fpreprocessed report-gcc16.ii -quiet -dumpbase report-gcc16.cpp -dumpbase-ext
.cpp -mtune=generic -march=x86-64 -O2 -Werror=array-bounds -std=c++20 -version
-o report-gcc16.s
GNU C++20 (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders, r1)
version 16.2.0 (x86_64-w64-mingw32)
        compiled by GNU C version 16.2.0, GMP version 6.3.0, MPFR version
4.2.2, MPC version 1.4.1, isl version isl-0.28-GMP

GGC heuristics: --param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: b99e3589a9faf7626d8db5f4c017c742
In file included from
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/stl_iterator.h:78,
                 from
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/stl_algobase.h:66,
                 from
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/array:45,
                 from repro.cpp:1:
In function 'constexpr void std::_Construct(_Tp*, _Args&& ...) [with _Tp =
byte; _Args = {const byte&}]',
    inlined from 'static constexpr void std::allocator_traits<
<template-parameter-1-1> >::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp =
std::byte; _Args = {const std::byte&}; _Alloc = Allocator<std::byte>]' at
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/alloc_traits.h:467:21,
    inlined from 'constexpr _ForwardIterator
std::__uninitialized_copy_a(_InputIterator, _Sentinel, _ForwardIterator,
_Allocator&) [with _InputIterator = __gnu_cxx::__normal_iterator<const byte*,
span<const byte>::__iter_tag>; _Sentinel = __gnu_cxx::__normal_iterator<const
byte*, span<const byte>::__iter_tag>; _ForwardIterator = byte*; _Allocator =
Allocator<byte>]' at
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/stl_uninitialized.h:627:21,
    inlined from 'constexpr void std::vector<_Tp,
_Alloc>::_M_range_insert(iterator, _ForwardIterator, _ForwardIterator,
std::forward_iterator_tag) [with _ForwardIterator =
__gnu_cxx::__normal_iterator<const std::byte*, std::span<const
std::byte>::__iter_tag>; _Tp = std::byte; _Alloc = Allocator<std::byte>]' at
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/vector.tcc:984:34,
    inlined from 'constexpr std::vector<_Tp, _Alloc>::iterator std::vector<_Tp,
_Alloc>::insert(const_iterator, _InputIterator, _InputIterator) [with
_InputIterator = __gnu_cxx::__normal_iterator<const std::byte*, std::span<const
std::byte>::__iter_tag>; <template-parameter-2-2> = void; _Tp = std::byte;
_Alloc = Allocator<std::byte>]' at
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/stl_vector.h:1611:19,
    inlined from 'void Stream::write(std::span<const std::byte>)' at
repro.cpp:20:21,
    inlined from 'void Record::serialize(Stream&) const' at repro.cpp:31:16,
    inlined from 'void reproduce(const Record&)' at repro.cpp:40:21:
D:/work/bitcoin-wallet/gcc-array-bounds-repro/toolchains/gcc16.2/mingw64/include/c++/16.2.0/bits/stl_construct.h:133:7:
error: 'void* __builtin_memcpy(void*, const void*, long long unsigned int)'
offset [5, 8] is out of the bounds [0, 5] [-Werror=array-bounds=]
  133 |       ::new(static_cast<void*>(__p))
_Tp(std::forward<_Args>(__args)...);
      |      
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1plus.exe: some warnings being treated as errors
  • [Bug tree-optimization/127625] N... hello at davidmolnar dot dev via Gcc-bugs

Reply via email to