Fixture 142

nonlocal control

C · 2 functions · 4 lanes · 7 of 8 function-lanes behave identically

One lane has a function that returns a different result after decompilation: clang-O2 (1/2).

setjmp/longjmp transfers control to a frame that the ordinary CFG shows no edge to: setjmp returns twice, and longjmp discards every frame in between. The jmp_buf holds the callee-saved registers and stack pointer, so anything that assumes a single linear return path recovers this wrongly.

tests/decompiler_fixtures/src/142_nonlocal_control.c source
#include <stdint.h>
#include <setjmp.h>

/* setjmp/longjmp transfers control to a frame that the ordinary CFG shows no
 * edge to: setjmp returns twice, and longjmp discards every frame in between.
 * The jmp_buf holds the callee-saved registers and stack pointer, so anything
 * that assumes a single linear return path recovers this wrongly. */

static void deep_thrower(jmp_buf target, int32_t depth, int32_t code) {
    if (depth <= 0) {
        longjmp(target, code); /* never returns */
    }
    /* A normal return path. Callers clamp depth to 8 so this is unreachable in
     * practice, but without it every path either recurses or longjmps and GCC
     * reports -Winfinite-recursion, which -Werror turns into a build failure. */
    if (depth > 8) {
        return;
    }
    deep_thrower(target, depth - 1, code);
}

__attribute__((noinline)) int32_t setjmp_returns_twice(int32_t code) {
    jmp_buf target;
    volatile int32_t visits = 0;
    int32_t landed;
    if (code < 1 || code > 8) {
        return -1;
    }
    visits += 1;
    landed = setjmp(target);
    if (landed == 0) {
        visits += 1;
        longjmp(target, code);
    }
    /* Reached only on the second return, with landed == code. */
    return landed * 10 + visits;
}

__attribute__((noinline)) int32_t longjmp_unwinds_frames(int32_t depth,
                                                         int32_t code) {
    jmp_buf target;
    int32_t landed;
    if (depth < 0 || depth > 8 || code < 1 || code > 8) {
        return -1;
    }
    landed = setjmp(target);
    if (landed == 0) {
        deep_thrower(target, depth, code);
        return -2; /* unreachable: deep_thrower always jumps */
    }
    return landed * 100 + depth;
}

Recovered C

Generated by glaurung decompile --style decbench at b47f6b43. baseline.json records the result after recompiling the C and calling it beside the original with seeded inputs.

clang -O2

1/2
longjmp_unwinds_frames fail 42 lines
// glaurung: longjmp_unwinds_frames @ 0x1180
int32_t longjmp_unwinds_frames(int32_t arg0, int32_t arg1) {
    extern long _setjmp(long *);
    extern __attribute__((noreturn)) long longjmp(long *, long);
    int landed;
    long local_10;
    long ret;
    long rsp;
    long var0;
    long var10;
    int var13;
    long var3;
    long var7;
    long var8;
    rsp = (rsp - 8);
    rsp = (rsp - 8);
    local_10 = var0;
    rsp = (rsp - 200);
    ret = 0xffffffff;
    if (((unsigned long)(8) < (unsigned long)((unsigned long)((unsigned int)(arg0))))) {
        rsp = (rsp + 200);
        // x86-64 epilogue: tear down frame
        return ret;
    }
    var3 = (unsigned long)((unsigned int)(arg1));
    if (((unsigned long)((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) - 9)))) < (unsigned long)(0xfffffff8))) {
        rsp = (rsp + 200);
        // x86-64 epilogue: tear down frame
        return ret;
    }
    var7 = (unsigned long)((unsigned int)(arg0));
    var8 = _setjmp((long *)(rsp));
    landed = var8;
    if (((unsigned long)((unsigned int)(var8)) == 0)) {
        var10 = longjmp((long *)(rsp), (unsigned long)((unsigned int)(var3)));
    }
    var13 = ((landed * 100) + var7);
    ret = (unsigned long)((unsigned int)(var13));
    rsp = (rsp + 200);
    // x86-64 epilogue: tear down frame
    return (unsigned int)(var13);
}
setjmp_returns_twice pass 36 lines
// glaurung: setjmp_returns_twice @ 0x1120
__attribute__((no_stack_protector)) int32_t setjmp_returns_twice(int32_t arg0) {
    extern long _setjmp(void *);
    extern __attribute__((noreturn)) long longjmp(void *, long);
    int landed;
    long local_8;
    unsigned char local_d8[208];
    int local_dc;
    long ret;
    long var0;
    int var12;
    long var4;
    long var5;
    long var7;
    long var9;
    local_8 = var0;
    local_dc = 0;
    ret = 0xffffffff;
    if (((unsigned long)((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) - 9)))) < (unsigned long)(0xfffffff8))) {
        // x86-64 epilogue: tear down frame
        return ret;
    }
    var4 = (unsigned long)((unsigned int)(arg0));
    local_dc = (local_dc + 1);
    var5 = _setjmp(&local_d8[0]);
    landed = var5;
    if (((unsigned long)((unsigned int)(var5)) == 0)) {
        local_dc = (local_dc + 1);
        var7 = longjmp(&local_d8[0], (unsigned long)((unsigned int)(var4)));
    }
    var9 = (unsigned long)((unsigned int)((landed + (landed * 4))));
    var12 = ((unsigned int)((var9 + var9)) + local_dc);
    ret = (unsigned long)((unsigned int)(var12));
    // x86-64 epilogue: tear down frame
    return (unsigned int)(var12);
}

clang -O0

2/2
longjmp_unwinds_frames pass 38 lines
// glaurung: longjmp_unwinds_frames @ 0x11d0
__attribute__((no_stack_protector)) int32_t longjmp_unwinds_frames(int32_t arg0, int32_t arg1) {
    extern long _setjmp(void *);
    extern void deep_thrower(char *, int, int);
    int landed;
    int local_4;
    unsigned char local_e0[200];
    long var0;
    // x86-64 prologue: save rbp, frame 240 bytes
    if (((long)(arg0) < 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((((unsigned long)((unsigned int)(arg0)) == 8) | ((long)(arg0) < 8)) == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((long)(arg1) < 1)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((((unsigned long)((unsigned int)(arg1)) == 8) | ((long)(arg1) < 8)) == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    var0 = _setjmp(&local_e0[0]);
    landed = var0;
    if (((unsigned long)((unsigned int)(landed)) != 0)) {
        return (unsigned int)(((landed * 100) + arg0));
    } else {
        deep_thrower((char *)(&local_e0[0]), (unsigned long)((unsigned int)(arg0)), (unsigned long)((unsigned int)(arg1)));
        return (unsigned int)(-2);
    }
}
setjmp_returns_twice pass 33 lines
// glaurung: setjmp_returns_twice @ 0x1120
__attribute__((no_stack_protector)) int32_t setjmp_returns_twice(int32_t arg0) {
    extern long _setjmp(void *);
    extern __attribute__((noreturn)) long longjmp(void *, long);
    int visits;
    int landed;
    int local_4;
    unsigned char local_d0[200];
    long var3;
    long var8;
    // x86-64 prologue: save rbp, frame 224 bytes
    visits = 0;
    if (((long)(arg0) < 1)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    if (((((unsigned long)((unsigned int)(arg0)) == 8) | ((long)(arg0) < 8)) == 0)) {
        local_4 = -1;
        // x86-64 epilogue: restore rbp
        return (unsigned int)(local_4);
    }
    visits = ((unsigned int)(visits) + 1);
    var3 = _setjmp(&local_d0[0]);
    landed = var3;
    if (((unsigned long)((unsigned int)(landed)) == 0)) {
        visits = ((unsigned int)(visits) + 1);
        var8 = longjmp(&local_d0[0], (unsigned long)((unsigned int)(arg0)));
    }
    local_4 = ((landed * 10) + (unsigned int)(visits));
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4);
}

gcc -O0

2/2
longjmp_unwinds_frames pass 30 lines
// glaurung: longjmp_unwinds_frames @ 0x1275
int32_t longjmp_unwinds_frames(int32_t arg0, int32_t arg1) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    extern long _setjmp(void *);
    extern void deep_thrower(char *, int, int);
    int landed;
    long local_8;
    unsigned char local_d0[200];
    long ret;
    long var4;
    // x86-64 prologue: save rbp, frame 240 bytes
    local_8 = (long)(0x28);
    if ((((((long)(arg0) < 0) || ((((unsigned long)((unsigned int)(arg0)) == 8) | ((long)(arg0) < 8)) == 0)) || (((unsigned long)((unsigned int)(arg1)) == 0) | ((long)(arg1) < 0))) || (((unsigned long)((unsigned int)(arg1)) != 8) && (8 <= (long)(arg1))))) {
        ret = 0xffffffff;
    } else {
        var4 = _setjmp(&local_d0[0]);
        landed = var4;
        if (((unsigned long)((unsigned int)(landed)) != 0)) {
            ret = (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) + ((unsigned long)((unsigned int)(landed)) * 100))));
        } else {
            deep_thrower((char *)(&local_d0[0]), (unsigned long)((unsigned int)(arg0)), (unsigned long)((unsigned int)(arg1)));
            ret = 0xfffffffe;
        }
    }
    if ((local_8 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}
setjmp_returns_twice pass 41 lines
// glaurung: setjmp_returns_twice @ 0x11a8
int32_t setjmp_returns_twice(int32_t arg0) {
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    extern long _setjmp(void *);
    extern __attribute__((noreturn)) long longjmp(void *, long);
    int visits;
    int landed;
    long local_8;
    unsigned char local_d0[200];
    long ret;
    long var14;
    int var20;
    long var7;
    local_8 = (long)(0x28);
    visits = 0;
    if (((((unsigned long)((unsigned int)(arg0)) == 0) | ((long)(arg0) < 0)) == 0)) {
        if ((((unsigned long)((unsigned int)(arg0)) == 8) | ((long)(arg0) < 8))) {
            goto L_11ef;
        }
    }
    ret = 0xffffffff;
    goto L_125f;
    L_11ef: ;
    visits = ((unsigned int)(visits) + 1);
    var7 = _setjmp(&local_d0[0]);
    landed = var7;
    if (((unsigned long)((unsigned int)(landed)) == 0)) {
        visits = ((unsigned int)(visits) + 1);
        var14 = longjmp(&local_d0[0], (unsigned long)((unsigned int)(arg0)));
    }
    var20 = ((unsigned int)(((unsigned long)((unsigned int)(landed)) << 2)) + (unsigned int)(landed));
    ret = (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(visits)) + (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(var20)) + (unsigned long)((unsigned int)(var20))))))));
    L_125f: ;
    if ((local_8 == 0x28)) {
        // x86-64 epilogue: restore rbp
        return ret;
    }
    __stack_chk_fail();
    // x86-64 epilogue: restore rbp
    return ret;
}

gcc -O2

2/2
longjmp_unwinds_frames pass 37 lines
// glaurung: longjmp_unwinds_frames @ 0x1210
int32_t longjmp_unwinds_frames(int32_t arg0, int32_t arg1) {
    extern long __longjmp_chk(void *, long);
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    extern long _setjmp(void *);
    int landed;
    long local_10;
    unsigned char local_d8[200];
    long ret;
    long var10;
    long var4;
    local_10 = (long)(0x28);
    if (((unsigned long)(7) < (unsigned long)((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) - 1)))))) {
        goto L_1277;
    }
    if (((unsigned long)(8) < (unsigned long)((unsigned long)((unsigned int)(arg0))))) {
        goto L_1277;
    }
    var4 = _setjmp(&local_d8[0]);
    landed = var4;
    if (((unsigned long)((unsigned int)(var4)) == 0)) {
        goto L_1280;
    }
    ret = (unsigned long)((unsigned int)(((landed * 100) + arg0)));
    L_125c: ;
    if ((local_10 != 0x28)) {
        goto L_128e;
    }
    return ret;
    L_1277: ;
    ret = 0xffffffff;
    goto L_125c;
    L_1280: ;
    var10 = __longjmp_chk(&local_d8[0], (unsigned long)((unsigned int)(arg1)));
    L_128e: ;
    __stack_chk_fail();
}
setjmp_returns_twice pass 38 lines
// glaurung: setjmp_returns_twice @ 0x1160
int32_t setjmp_returns_twice(int32_t arg0) {
    extern long __longjmp_chk(void *, long);
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    extern long _setjmp(void *);
    int visits;
    int landed;
    long local_10;
    unsigned char local_d8[200];
    long ret;
    long var18;
    long var9;
    local_10 = (long)(0x28);
    visits = 0;
    if (((unsigned long)(7) < (unsigned long)((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg0)) - 1)))))) {
        goto L_11e0;
    }
    visits = ((unsigned int)(visits) + 1);
    var9 = _setjmp(&local_d8[0]);
    landed = var9;
    if (((unsigned long)((unsigned int)(var9)) == 0)) {
        goto L_11ec;
    }
    ret = (unsigned long)((unsigned int)(((unsigned long)((unsigned int)(visits)) + ((unsigned long)((unsigned int)((landed + (landed * 4)))) * 2))));
    L_11bb: ;
    if ((local_10 != 0x28)) {
        goto L_11e7;
    }
    return ret;
    L_11e0: ;
    ret = 0xffffffff;
    goto L_11bb;
    L_11e7: ;
    __stack_chk_fail();
    L_11ec: ;
    visits = ((unsigned int)(visits) + 1);
    var18 = __longjmp_chk(&local_d8[0], (unsigned long)((unsigned int)(arg0)));
}

← 213 fixtures