Incorrect integer promotion on bit-fields

void printf();
struct {
  unsigned b : 2;
} c = {0};

int main() {
  printf("%llx\n", ~0 < c.b);

--- output.expected     2019-04-18 23:59:57.875958512 +0000
+++ output.actual       2019-04-18 23:59:57.959958695 +0000
@@ -1 +1 @@
Assigned to
8 months ago
8 months ago

~mcf 8 months ago

Hmm... this one took me a bit to figure out.

~0 has type int and value -1. My initial understanding is that c.b had type unsigned, so was unaffected by integer promotions, and then usual arithmetic conversions would convert the left operand to unsigned, resulting in 0xffffffff < 0 == 0.

However, reading integer promotion rules more carefully, I see that there is special consideration for bit-fields:

If an int can represent all values of the original type (as restricted by the width, for a bit-field), the value is converted to an int; otherwise, it is converted to an unsigned int.

Since int can represent all values of the b bitfield, c.b should have type int after integer promotion.

Thanks for finding these bugs. These are pretty subtle issues I probably wouldn't have found on my own.

~ach 8 months ago

No problem - As you said before, the main trick is just having a good 'interesting' function and getting things set up, after that I just have to alt-tab and let csmith do the work.

~mcf 8 months ago

Interestingly, it looks like regardless of the bit-field type, they can still be promoted to int if they have a smaller width than int.

So, even if the type of b was changed to unsigned long, this still prints 1.

~mcf 8 months ago

Results of some experimentation:

struct {unsigned long x :31;} s1;
struct {unsigned long x :32;} s2;
struct {unsigned long x :31;} s3;
int c1 = __builtin_types_compatible_p(__typeof__(+s1.x), int);
int c2 = __builtin_types_compatible_p(__typeof__(+s2.x), unsigned);
int c3 = __builtin_types_compatible_p(__typeof__(+s3.x), unsigned long);

On clang, all of c1, c2, and c3 are 1. On gcc, c1 and c2 are 1, while c3 is 0.

So far, I've been unable to determine the type gcc uses for +s3.x, and I'm not sure of any debugging options to figure this out.

But I think I understand clang's behavior, so matching that probably makes sense.

~mcf 8 months ago

Whoops, typo, that should be x : 33 for s3.

~mcf 8 months ago


struct {unsigned long x :31;} s1;
struct {unsigned long x :32;} s2;
struct {unsigned long x :33;} s3;
__typeof__(+s1.x) t1;
__typeof__(+s2.x) t2;
__typeof__(+s3.x) t3;
int main(void) {
$ gcc -g test.c                                                                             
$ gdb -batch -ex 'whatis t1' -ex 'whatis t2' -ex 'whatis t3' ./a.out
type = int
type = unsigned int
type = __unknown__
$ clang -g test.c
$ gdb -batch -ex 'whatis t1' -ex 'whatis t2' -ex 'whatis t3' ./a.out
type = int
type = unsigned int
type = unsigned long

So gcc must use some internal type for bit-field promotion when it is wider than int.

~mcf 8 months ago

$ cparser -g test.c
$ gdb -batch -ex 'whatis t1' -ex 'whatis t2' -ex 'whatis t3' ./a.out
type = int
type = unsigned long
type = unsigned long

The standard only specifies bit-fields of type _Bool, signed int, and unsigned int, but permits other implementation-defined types. It seems there isn't a consensus of integer promotion rules for those types.

~mcf REPORTED FIXED 8 months ago

Fixed by a98385a6.

Register here or Log in to comment, or comment via email.