I requested a bugzilla account a few days ago and haven't heard back so I'm just going to report this here.
#include <stdbool.h> #include <stdint.h> #include <stdio.h> bool add(uint8_t *r, const uint8_t *a, const uint8_t *b) { return __builtin_add_overflow(*a, *b, r); } bool mul(uint8_t *r, const uint8_t *a, const uint8_t *b) { return __builtin_mul_overflow(*a, *b, r); } int main() { uint8_t x; /* 64 + 64 should not overflow */ x = 64; if (add(&x, &x, &x)) printf("false positive: x=%i\n", x); /* 4 * 4 should not overflow */ x = 4; if (mul(&x, &x, &x)) printf("false positive: x=%i\n", x); /* 128 + 128 should overflow */ x = 128; if (!add(&x, &x, &x)) printf("false negative: x=%i\n", x); /* 16 * 16 should overflow */ x = 16; if (!mul(&x, &x, &x)) printf("false negative: x=%i\n", x); return 0; } $ gcc -g3 -O1 -o test test.c; ./test false positive: x=128 false positive: x=16 false negative: x=0 false negative: x=0 The generated assembly correctly adds a with b and stores in r but then it does the operation again before testing for carry and returning. If r is also one of the operands then the erroneous second operation will use the computed value of r in place of the original operand leading to an incorrect overflow result. 0000000000400895 <add>: ; return __builtin_add_overflow(*a, *b, r); 400895: 0f b6 02 movzbl (%rdx), %eax 400898: 02 06 addb (%rsi), %al 40089a: 88 07 movb %al, (%rdi) 40089c: 0f b6 02 movzbl (%rdx), %eax << 40089f: 02 06 addb (%rsi), %al << 4008a1: 0f 92 c0 setb %al ; } 4008a4: c3 retq 00000000004008a5 <mul>: ; return __builtin_mul_overflow(*a, *b, r); 4008a5: 0f b6 06 movzbl (%rsi), %eax 4008a8: f6 22 mulb (%rdx) 4008aa: 88 07 movb %al, (%rdi) 4008ac: 0f b6 06 movzbl (%rsi), %eax << 4008af: f6 22 mulb (%rdx) << 4008b1: 0f 90 c0 seto %al ; } 4008b4: c3 retq This only seems to be triggered when both a *and* b are const. Removing const from either or both operands generates the correct assembly. 0000000000400855 <add>: ; return __builtin_add_overflow(*a, *b, r); 400855: 0f b6 02 movzbl (%rdx), %eax 400858: 02 06 addb (%rsi), %al 40085a: 88 07 movb %al, (%rdi) 40085c: 0f 92 c0 setb %al ; } 40085f: c3 retq 0000000000400860 <mul>: ; return __builtin_mul_overflow(*a, *b, r); 400860: 0f b6 06 movzbl (%rsi), %eax 400863: f6 22 mulb (%rdx) 400865: 88 07 movb %al, (%rdi) 400867: 0f 90 c0 seto %al ; } 40086a: c3 retq Tested on the following systems: $ uname -srm; gcc12 --version FreeBSD 13.1-RELEASE-p2 amd64 gcc12 (FreeBSD Ports Collection) 12.2.0 $ uname -smr; gcc --version Linux 6.1.2-1-MANJARO-ARM aarch64 gcc (GCC) 12.1.0 $ uname -srm; gcc --version MINGW64_NT-10.0-22000 3.4.5.x86_64 x86_64 gcc.exe (Rev10, Built by MSYS2 project) 12.2.0 Poking godbolt indicates this was likely introduced in 9.4.