llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
26.71k stars 10.94k forks source link

Compiler crash #31763

Open llvmbot opened 7 years ago

llvmbot commented 7 years ago
Bugzilla Link 32416
Version unspecified
OS All
Reporter LLVM Bugzilla Contributor
CC @asl,@DimitryAndric,@zmodem,@rotateright,@vedantk

Extended Description

(I originally filed this in Apple's bug reporting system as 31009924, but I'm not getting any response there, so I figured I would post it here instead. If this is the wrong place, I'm sorry.)

Using the provided source file, the compiler (Apple LLVM version 8.0.0 (clang-800.0.42.1)) crashes.

Please note that I'm fully aware that the provided code is not meaningful, it's just a very condensed version of the actual source file that causes the compiler crash.

Steps to Reproduce:

$ cat /tmp/test.c

include

include

include

typedef struct typeA { int placeholder; } typeA; typedef struct typeB { int placeholder; typeA a; } typeB; typedef struct typeC { typeB b; } typeC; typedef struct typeD { int placeholder; typeC z[2]; } typeD; typedef struct typeE { typeB b; typeA a; } typeE; static void init2(typeE const s, typeB const b) { b->a = s->a; }

define NUM_BYTES (sizeof(typeB) 2 2)

static void init1(typeD const t, uint8_t const mem_in, const ptrdiff_t sz) { uint8_t mem = mem_in; typeE s; uint8_t ptrs[2], **ptr = ptrs;

*ptr = mem; mem += NUM_BYTES;
s.b = (typeB *) *ptr++;
*ptr = mem;
s.a = NULL;

int b, e;
for (b = 0; b < 2; b++) {
    t->z[b].b = s.b;
    for (e = 0; e < 2; e++)
        init2(&s, &t->z[b].b[e]);
    s.b += 2;
}

ptr = &ptrs[1];
assert(*ptr++ == (uint8_t *) s.b);

}

int main(void) { const ptrdiff_t sz = NUM_BYTES; uint8_t *const mem = malloc(sz); init1(NULL, mem, sz); return 0; }

$ gcc -o /tmp/test.o -c /tmp/test.c -O3 clang: error: unable to execute command: Segmentation fault: 11 clang: error: clang frontend command failed due to signal (use -v to see invocation) Apple LLVM version 8.0.0 (clang-800.0.42.1) Target: x86_64-apple-darwin16.4.0 Thread model: posix InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin clang: note: diagnostic msg: PLEASE submit a bug report to http://developer.apple.com/bugreporter/ and include the crash backtrace, preprocessed source, and associated run script. clang: note: diagnostic msg:


PLEASE ATTACH THE FOLLOWING FILES TO THE BUG REPORT: Preprocessed source(s) and associated run script(s) are located at: clang: note: diagnostic msg: /var/folders/fz/vjyfg5z12vj759mhd1t27r_m0000gn/T/test-287e75.c clang: note: diagnostic msg: /var/folders/fz/vjyfg5z12vj759mhd1t27r_m0000gn/T/test-287e75.sh clang: note: diagnostic msg:


zmodem commented 6 years ago

It would be good to get some clarity here before the release.

llvmbot commented 7 years ago

There doesn't seem to be a compiler bug here: this code violates C's aliasing rules. A load from a i8* isn't allowed to alias a load from a struct.typeB

I looked this up, and the standard (c99) makes a specific exception for (unsigned) char, doesn't it? See quoted text below.

"An object shall have its stored value accessed only by an lvalue expression that has one of the following types:

struct.typeB is "a type compatible with the effective type of the object", and uint8_t (i8) is a "character type". Can you help me explain the spec violation of my code?

Regardless, let's assume you're right just for the sake of argument. The standard recommended workaround for strict aliasing violations (type punning) is to use unions. If I remove all (2) casts in my code and replace them with unions, the assert still triggers. Therefore, I believe your analysis is incorrect and there is a compiler bug here.

$ cat /tmp/y.c

include

include

include

include

typedef struct typeA { int placeholder; } typeA; typedef struct typeB { int placeholder; typeA a; } typeB; typedef struct typeC { typeB b; } typeC; typedef struct typeD { int placeholder; typeC z[2]; } typeD; typedef struct typeE { typeB b; typeA a; } typeE; static void init2(typeE const s, typeB const b) { b->a = s->a; }

define NUM_BYTES (sizeof(typeB) 2 2)

static void init1(typeD const t, uint8_t const mem_in, const ptrdiff_t sz) { uint8_t mem = mem_in; typeE s; uint8_t ptrs[2];

ptrs[0] = mem; mem += NUM_BYTES;
s.b = (union { typeB *b; uint8_t *u; }) { .u = ptrs[0] }.b;
ptrs[1] = mem;
s.a = NULL;

int b, e;
for (b = 0; b < 2; b++) {
    t->z[b].b = s.b;
    for (e = 0; e < 2; e++)
        init2(&s, &t->z[b].b[e]);
    s.b += 2;
}

printf("assert(%p == %p);\n", ptrs[1], s.b);
assert(ptrs[1] == (union { typeB *b; uint8_t *u; }) { .b = s.b }.u);

}

int main(void) { const ptrdiff_t sz = NUM_BYTES; typeD d = malloc(sizeof(typeD)); uint8_t const mem = malloc(sz); init1(d, mem, sz); return 0; }

$ clang-mp-4.0 --version clang version 4.0.0 (branches/release_40 292772) Target: x86_64-apple-darwin16.4.0 Thread model: posix InstalledDir: /opt/local/libexec/llvm-4.0/bin

$ gcc-mp-4.8 --version gcc-mp-4.8 (MacPorts gcc48 4.8.5_0) 4.8.5 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ clang-mp-4.0 -o /tmp/y /tmp/y.c -O3 -Wall && /tmp/y assert(0x7fd63e4028a0 == 0x7fd63e4028a0); Assertion failed: (ptrs[1] == (union { typeB b; uint8_t u; }) { .b = s.b }.u), function init1, file /tmp/y.c, line 34. Abort trap: 6

$ clang-mp-4.0 -o /tmp/y /tmp/y.c -O1 -Wall && /tmp/y assert(0x7fd8be4028c0 == 0x7fd8be4028c0);

$ gcc-mp-4.8 -o /tmp/y /tmp/y.c -O3 -Wall && /tmp/y assert(0x7ff1224028a0 == 0x7ff1224028a0);

llvmbot commented 7 years ago

Sorry that was with Apple's version of clang, I keep making that mistake, sorry about that. But the same happens with the 4.0 release of clang-upstream from Macports:

$ clang-mp-4.0 -o /tmp/x -O3 -fno-strict-aliasing /tmp/x.c && /tmp/x assert(0x7fb5864028a0 == 0x7fb5864028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6

llvmbot commented 7 years ago

Try adding -fno-strict-aliasing to your optimization flags.

The assert still triggers when using that flag:

$ gcc -o /tmp/x -O3 -fno-strict-aliasing /tmp/x.c && /tmp/x assert(0x7fa8a7c028a0 == 0x7fa8a7c028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6

DimitryAndric commented 7 years ago

Maybe the compiler should warn about that? Even with -Wall, I get no warnings at all.

Generating warnings for this is not easy, will very likely have false positives, and will definitely not catch all aliasing abuses. Plus it would break many existing projects out there. :)

Any suggestions on how to change the code so it's aliasing-compliant yet still allows me to compare pointers?

Try adding -fno-strict-aliasing to your optimization flags.

llvmbot commented 7 years ago

Maybe the compiler should warn about that? Even with -Wall, I get no warnings at all. Any suggestions on how to change the code so it's aliasing-compliant yet still allows me to compare pointers?

vedantk commented 7 years ago

There doesn't seem to be a compiler bug here: this code violates C's aliasing rules. A load from a i8* isn't allowed to alias a load from a struct.typeB, so the compiler throws away the pointer comparison.

Here's the IR:

define i32 @​main() local_unnamed_addr #​0 { %1 = tail call i8 @​calloc(i64 1, i64 64) %2 = getelementptr inbounds i8, i8 %1, i64 64 %3 = getelementptr inbounds %struct.typeB, i8 %1, i64 4 %4 = tail call i32 (i8, ...) @​printf(i8 getelementptr inbounds ([19 x i8], [19 x i8] @.str, i64 0, i64 0), i8 %2, %struct.typeB %3) #​3 tail call void @​__assert_rtn(i8 getelementptr inbounds ([6 x i8], [6 x i8] @​func.init1, i64 0, i64 0), i8 getelementptr inbounds ([9 x i8], [9 x i8] @.str.1, i64 0, i64 0), i32 34, i8 getelementptr inbounds ([27 x i8], [27 x i8] @.str.2, i64 0, i64 0)) #​4 unreachable }

llvmbot commented 7 years ago

And some further info, it goes away when reducing -O from 2 to 1:

$ clang-mp-4.0 -o /tmp/x -O2 /tmp/x.c

$ /tmp/x assert(0x7fcc144028a0 == 0x7fcc144028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6

$ clang-mp-4.0 -o /tmp/x -O1 /tmp/x.c

$ /tmp/x assert(0x7f8b0c600060 == 0x7f8b0c600060);

$ clang-mp-4.0 -o /tmp/x -O0 /tmp/x.c

$ /tmp/x assert(0x7fe6194028c0 == 0x7fe6194028c0);

llvmbot commented 7 years ago

Hey guys,

I'm really terribly sorry to reopen this bug. Let me explain: Apple just released XCode 8.3 (with their "clang 8.1") and it fixed the compiler crash. However, the compiler (still?) miscompiles the code. What's more interesting is that the miscompilation reproduces in upstream clang! I'm hoping this time the bug report is acceptable.

(Below, "clang-mp-4.0" is clang-4.0 installed from MacPorts, and gcc-mp-4.8 is gcc-4.8 installed from MacPorts.)

===

$ clang-mp-4.0 --version clang version 4.0.0 (branches/release_40 292772) Target: x86_64-apple-darwin16.4.0 Thread model: posix InstalledDir: /opt/local/libexec/llvm-4.0/bin

$ cat /tmp/x.c

include

include

include

include

typedef struct typeA { int placeholder; } typeA; typedef struct typeB { int placeholder; typeA a; } typeB; typedef struct typeC { typeB b; } typeC; typedef struct typeD { int placeholder; typeC z[2]; } typeD; typedef struct typeE { typeB b; typeA a; } typeE; static void init2(typeE const s, typeB const b) { b->a = s->a; }

define NUM_BYTES (sizeof(typeB) 2 2)

static void init1(typeD const t, uint8_t const mem_in, const ptrdiff_t sz) { uint8_t mem = mem_in; typeE s; uint8_t ptrs[2];

ptrs[0] = mem; mem += NUM_BYTES;
s.b = (typeB *) ptrs[0];
ptrs[1] = mem;
s.a = NULL;

int b, e;
for (b = 0; b < 2; b++) {
    t->z[b].b = s.b;
    for (e = 0; e < 2; e++)
        init2(&s, &t->z[b].b[e]);
    s.b += 2;
}

printf("assert(%p == %p);\n", ptrs[1], s.b);
assert(ptrs[1] == (uint8_t *) s.b);

}

int main(void) { const ptrdiff_t sz = NUM_BYTES; typeD d = malloc(sizeof(typeD)); uint8_t const mem = malloc(sz); init1(d, mem, sz); return 0; }

$ clang-mp-4.0 -o /tmp/x -O3 /tmp/x.c

$ /tmp/x assert(0x7f84144028a0 == 0x7f84144028a0); Assertion failed: (ptrs[1] == (uint8_t *) s.b), function init1, file /tmp/x.c, line 34. Abort trap: 6

$ gcc-mp-4.8 --version gcc-mp-4.8 (MacPorts gcc48 4.8.5_0) 4.8.5 Copyright (C) 2015 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ gcc-mp-4.8 -o /tmp/x -O3 /tmp/x.c

$ /tmp/x assert(0x7f7f944028a0 == 0x7f7f944028a0);

rotateright commented 7 years ago

For future reference, you can try compiling your code here: https://godbolt.org/g/aQ3vUm

To see if you can induce any open-source clang release to reproduce the crash (I couldn't - although I had to modify the code to get it to compile it all there).

If you can show a failure there, we could potentially bisect that failure to some commit in trunk.

llvmbot commented 7 years ago

OK, thanks for testing, sorry for the noise... How shall I close it?

asl commented 7 years ago

Cannot reproduce with 4.0 and ToT, so it's Apple bug.

llvmbot commented 11 months ago

@llvm/issue-subscribers-c

Endilll commented 11 months ago

The first test case reduced by C-Reduce:

typedef struct {
  int *b
} typeE;
*init1_mem_in;
main_b;
main() {
  char *mem = init1_mem_in;
  typeE s;
  mem += sizeof(0);
  s.b = init1_mem_in;
  int e;
  main_b = 0;
  for (; main_b < 1; main_b++) {
    for (; e;)
      init2(&s);
    s.b++;
  }
  if (mem == s.b)
    __assert_fail();
}