gcc-15 bugs, pile 2

April 19, 2025

8 more months have passed since my previous pile report. gcc-15 was branched off from master and will receive only regression fixes. master is called gcc-16 now.

It’s a good time to look at the compiler bugs I encountered.

summary

I got about 30 of those:

fun bugs

e2fsprogs bug

The e2fsprogs bug was an interesting case of wrong code. This was enough to trigger it:

// $ cat bug.c
__attribute__((noipa, optimize(1)))
void bug_o1(unsigned int nr, void * addr)
{
        unsigned char   *ADDR = (unsigned char *) addr;

        ADDR += nr >> 3;
        *ADDR &= (unsigned char) ~(1 << (nr & 0x07));
}

__attribute__((noipa, optimize(2)))
void bug_o2(unsigned int nr, void * addr)
{
        unsigned char   *ADDR = (unsigned char *) addr;

        ADDR += nr >> 3;
        *ADDR &= (unsigned char) ~(1 << (nr & 0x07));
}

int main() {
  void * bmo1 = __builtin_malloc(1024);
  void * bmo2 = __builtin_malloc(1024);
  for (unsigned bno = 0; bno < 1024 * 8; ++bno) {
    __builtin_memset(bmo1, 0xff, 1024);
    __builtin_memset(bmo2, 0xff, 1024);
    bug_o1(bno, bmo1);
    bug_o2(bno, bmo2);
    if (__builtin_memcmp(bmo1, bmo2, 1024) != 0)
      __builtin_trap();
  }
}

Crashing as:

$ gcc bug.c -o bug -O0 && ./bug
Illegal instruction (core dumped)

The gcc fix amends mask calculation as:

--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -18168,7 +18168,8 @@
  [(set (match_dup 4) (match_dup 1))
   (set (match_dup 0)
        (any_rotate:SWI (match_dup 4)
-		       (subreg:QI (match_dup 2) 0)))]
+		       (subreg:QI
+			 (and:SI (match_dup 2) (match_dup 3)) 0)))]
  "operands[4] = gen_reg_rtx (<MODE>mode);")
 
 (define_insn_and_split "*<insn><mode>3_mask_1"
@@ -18202,7 +18203,8 @@
   == GET_MODE_BITSIZE (<MODE>mode) - 1"
  [(set (match_dup 4) (match_dup 1))
   (set (match_dup 0)
-       (any_rotate:SWI (match_dup 4) (match_dup 2)))]
+       (any_rotate:SWI (match_dup 4)
+		       (and:QI (match_dup 2) (match_dup 3))))]
  "operands[4] = gen_reg_rtx (<MODE>mode);")
 
 (define_insn_and_split "*<insn><mode>3_add"

Here gcc incorrectly compiled bug_o2() into a single btr instruction. gcc assumed btr performs a typical 8-bit mask on register operand like other instructions do. But in case of btr it’s a 3/4/5-bit mask (for 8/16/32-bit offsets).

mesonlsp bug

The mesonlsp bug was also interesting. There seemingly trivial code:

// $ cat bug.cpp
#include <string>
#include <vector>

int main(){
  for (const auto &vec : std::vector<std::vector<std::string>>{
           {"aaa"},
       }) {
  }
}

crashed at runtime:

# ok
$ g++ bug.cpp -o bug -fsanitize=address
$ ./bug

# bad:
$ g++ bug.cpp -o bug -fsanitize=address -std=c++23
$ ./bug

=================================================================
==3828042==ERROR: AddressSanitizer: heap-use-after-free on address 0x7ba90dbe0040 at pc 0x000000404279 bp 0x7ffd9db5c110 sp 0x7ffd9db5c108
READ of size 8 at 0x7ba90dbe0040 thread T0
    #0 0x000000404278 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data() const (bug+0x404278)
...

0x7ba90dbe0040 is located 0 bytes inside of 32-byte region [0x7ba90dbe0040,0x7ba90dbe0060)
freed by thread T0 here:
    #0 0x7f790f1180c8 in operator delete(void*, unsigned long) (/<<NIX>>/gcc-15.0.1-lib/lib/libasan.so.8+0x1180c8)
    #1 0x000000406a4b in std::__new_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::deallocate(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, unsigned long) (bug+0x406a4b)
...

previously allocated by thread T0 here:
    #0 0x7f790f1171a8 in operator new(unsigned long) (/<<NIX>>/gcc-15.0.1-lib/lib/libasan.so.8+0x1171a8)
    #1 0x000000404c9f in std::__new_allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > >::allocate(unsigned long, void const*) (bug+0x404c9f)
...

SUMMARY: AddressSanitizer: heap-use-after-free (bug+0x404278) in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_data() const
Shadow bytes around the buggy address:
  0x7ba90dbdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ba90dbdfe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ba90dbdfe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ba90dbdff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x7ba90dbdff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x7ba90dbe0000: fa fa 00 00 00 fa fa fa[fd]fd fd fd fa fa fd fd
  0x7ba90dbe0080: fd fa fa fa fd fd fd fd fa fa fa fa fa fa fa fa
  0x7ba90dbe0100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7ba90dbe0180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7ba90dbe0200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x7ba90dbe0280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==3828042==ABORTING

It’s a use-after-free bug. Caused by the gcc bugs in temporary variables lifetime tracking. The gcc fixes (one, two) are not very small, thus I’ll not post them here.

histograms

As usual what are the subsystems we found the bugs in?

Surprisingly this time c++ is at the top of the list. It feels like coroutine related bugs pushed the needle. Otherwise middle-end and tree-optimization that follow are expected.

parting words

Of the bugs above it looks like I reported only 18 of those while 13 were already reported by others.

Optimised handling of global constant arrays (#embed-style code) caused numerous bugs in various subsystems from compiler crashes to wrong code.

The most disruptive change probably is the switch to c23.

Past month was very quiet from gcc bugs view. gcc-15 is in a good shape to be released.

Have fun!