10

I have mostly convinced myself that I have encountered some g++ 4.8.3 bug, but I thought I would ask this list first because I have very little experience with setjmp/longjmp. I have simplified my code in question to the following foo.cxx:

#include <setjmp.h>
#include <string.h>

// Changing MyStruct to be just a single int makes the compiler happy.
struct MyStruct
{
    int a;
    int b;
};

// Setting MyType to int makes the compiler happy.
#ifdef USE_STRUCT
typedef MyStruct MyType;
#elif USE_INT
typedef int MyType;
#endif

void SomeFunc(MyType val)
{
}

static void static_func(MyType val)
{
    SomeFunc(val);
}

int main(int argc, char **argv)
{
    jmp_buf env;
    if (setjmp(env))
    {
        return 1;
    }

    MyType val;
#ifdef USE_STRUCT
    val.a = val.b = 0;
#elif USE_INT
    val = 0;
#endif
    // Enabling the below memset call makes the compiler happy.
    //memset(&val, 0, sizeof(val));

    // Iterating 1 or 2 times makes the compiler happy.
    for (unsigned i = 0; i < 3; i++)
    {
        // calling SomeFunc() directly makes the compiler happy.
        static_func(val);
    }
    return 0;
}

I use g++ 4.8.3 to compile this code. What's interesting to me is that when I define USE_STRUCT, the compilation fails but succeeds with USE_INT. There are comments in the code that further indicate how to make compilation succeed with USE_STRUCT. Compilation only fails also with the -fPIC option to g++, but this is a required argument in my environment.

To see the compilation error:

g++ -DUSE_STRUCT -Wextra -Wno-unused-parameter -O3 -Werror -fPIC foo.cxx
foo.cxx: In function ‘int main(int, char**)’:
foo.cxx:26:5: error: variable ‘val’ might be clobbered by ‘longjmp’ or ‘vfork’ [-Werror=clobbered]

But using a simple int is OK:

g++ -DUSE_INT -Wextra -Wno-unused-parameter -O3 -Werror -fPIC foo.cxx

Can someone please explain to me why val might be clobbered if it is a struct but not if it is an int? Any insights on the other ways to make compilation succeed with the struct, as indicated in the comments in the code? Or is this pointing to a compiler bug?

Any insights and comments are greatly appreciated.

5gon12eder
  • 24,280
  • 5
  • 45
  • 92
boulderpika
  • 101
  • 1
  • 5
  • clobbering by `setjmp` etc is probably related to being in a register. – Basile Starynkevitch Jan 06 '15 at 16:29
  • 1
    Open bug that might be this one https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48968 – Richard Critten Jan 06 '15 at 16:36
  • 1
    Regarding the comment by Basile, what happens if you lower the optimization level? Have you checked out what assembly (or even intermediate) code the compiler generates? It might give you a hint of what's going on. – Some programmer dude Jan 06 '15 at 16:39
  • Compilation fails with any optimization level. Turn off optimization, and the compiler is happy. I have not looked at any assembly yet. – boulderpika Jan 06 '15 at 16:48
  • 1
    I cannot reproduce this with GCC 4.9.1 so it might be fixed (or perturbed out) there. It is not the case with your example and you probably already know but just for the record: exiting a scope that requires non-trivial stack unwinding via `longjmp` invokes undefined behavior in C++. – 5gon12eder Jan 06 '15 at 17:23

2 Answers2

5

setjmp() saves the current stack. Since it's called before the declaration of val, that variable won't be in the saved stack.

After setjmp(), the variable is initialized and if the code later jumps back to the setjmp point, the variable will be initialized again, clobbering the old variable. If there would be a non-trivial destructor that should be called on the old instance, this is undefined behavior (§18.10/4):

A setjmp/longjmp call pair has undefined behavior if replacing the setjmp and longjmp by catch and throw would invoke any non-trivial destructors for any automatic objects.

Likely the destructor of the old instance won't be called. My guess would be that gcc doesn't warn for primitive types, since they don't have destructors, but warns for more complicated types where this might be problematic.

sth
  • 222,467
  • 53
  • 283
  • 367
  • But the OP's `MyStruct` is a POD type so there is no initialization going on. Immediately after executing the line `MyStruct val;`, `val.a` and `val.b` are no more defined than after returning to it after a `longjmp`. – 5gon12eder Jan 06 '15 at 23:18
  • @5gon12eder: True, but you have to consider how hard it is for the compiler to determine whether the warning is justified. – MSalters Jan 07 '15 at 14:28
0

There are several factors at work here:

  1. struct instead of int
  2. not using memset (I admit I don't understand how this can make things worse)
  3. iterating the loop more than twice -- if you only iterate twice, the compiler unrolls the loop
  4. the -fPIC command-line option (this produces position-independent code)

Only if all four of these factors are present does the compiler produce a warning. It seems that they constitute a perfect storm for the optimiser, which has a kind of nervous breakdown (see below). If any of these factors is absent, the compiler just optimises everything away to nothing, so it can just ignore the setjmp.

Whether this is a bug is open to question -- the code presumably still works (although I haven't tested it). But in any case the problem seems to have been fixed in version 4.9, so the obvious solution is to upgrade.

Here is the machine code (NSFW):

SomeFunc(MyStruct):
    rep; ret
main:
    pushq   %r12
    pushq   %rbp
    pushq   %rbx
    subq    $224, %rsp
    leaq    16(%rsp), %rdi
    call    _setjmp@PLT
    testl   %eax, %eax
    movl    %eax, %ebp
    jne .L5
    movl    $3, %ebx
    movabsq $-4294967296, %r12
.L4:
    movq    8(%rsp), %rdx
    andq    %r12, %rdx
    movl    %edx, %eax
    movq    %rax, %rdi
    movq    %rax, 8(%rsp)
    call    SomeFunc(MyStruct)@PLT
    subl    $1, %ebx
    jne .L4
.L3:
    addq    $224, %rsp
    movl    %ebp, %eax
    popq    %rbx
    popq    %rbp
    popq    %r12
    ret
.L5:
    movl    $1, %ebp
    jmp .L3
TonyK
  • 16,761
  • 4
  • 37
  • 72