Fixture 133

cpp multiple inheritance

C++ · 3 functions · 4 lanes · 8 of 12 function-lanes behave identically

2 of 4 lanes have a function that returns a different result after decompilation: clang-O0 (1/3), gcc-O0 (1/3).

Multiple inheritance forces this-pointer adjustment: a Derived* converted to its second base points partway into the object, so the compiler emits thunks that add a fixed offset before jumping to the real body. Casting between base pointers therefore changes the numeric address, and comparing two base pointers of one object is NOT a plain address comparison.

tests/decompiler_fixtures/src/133_cpp_multiple_inheritance.cpp source
/* 133_cpp_multiple_inheritance.cpp
 *
 * Multiple inheritance forces `this`-pointer adjustment: a Derived* converted
 * to its second base points partway into the object, so the compiler emits
 * thunks that add a fixed offset before jumping to the real body. Casting
 * between base pointers therefore changes the numeric address, and comparing
 * two base pointers of one object is NOT a plain address comparison.
 */
#include <stdint.h>

namespace {

class Readable {
public:
    virtual ~Readable() {}
    virtual int32_t read(int32_t index) const { return index; }
};

class Writable {
public:
    virtual ~Writable() {}
    virtual int32_t write(int32_t index, int32_t value) { return index + value; }
    virtual int32_t flushed() const { return 0; }
};

class File : public Readable, public Writable {
public:
    int32_t stored;
    File() : stored(0) {}
    int32_t read(int32_t index) const override { return index * 2 + stored; }
    int32_t write(int32_t index, int32_t value) override {
        stored = index ^ value;
        return stored;
    }
    int32_t flushed() const override { return stored + 1; }
};

}  // namespace

extern "C" int32_t cpp_mi_dispatch(int32_t index, int32_t value) {
    File file;
    Readable *readable = &file;
    Writable *writable = &file; /* adjusted: not the same address */
    int32_t written = writable->write(index, value);
    int32_t got = readable->read(index);
    return written * 100 + got;
}

extern "C" int32_t cpp_mi_pointer_adjustment(int32_t seed) {
    File file;
    file.stored = seed;
    const Readable *readable = &file;
    const Writable *writable = &file;
    /* The two base subobjects live at different offsets, so the raw addresses
     * differ even though both denote the same complete object. */
    const void *first = static_cast<const void *>(readable);
    const void *second = static_cast<const void *>(writable);
    return (first != second) ? 1 : 0;
}

extern "C" int32_t cpp_mi_cross_cast(int32_t index, int32_t value) {
    File file;
    Writable *writable = &file;
    /* Cross-cast through the complete object, then dispatch on the far base. */
    File *complete = static_cast<File *>(writable);
    Readable *readable = complete;
    writable->write(index, value);
    return readable->read(index) + writable->flushed();
}

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 -O0

1/3
cpp_mi_cross_cast fail 42 lines
// glaurung: cpp_mi_cross_cast @ 0x1300
typedef struct File File;
typedef struct Readable Readable;
typedef struct Writable Writable;
struct File;
__attribute__((no_stack_protector)) int32_t cpp_mi_cross_cast(int32_t arg0, int32_t arg1) {
    extern void _ZN12_GLOBAL__N_14FileC2Ev(struct File *);
    extern void _ZN12_GLOBAL__N_14FileD2Ev(struct File *);
    unsigned char writable[8];
    unsigned char complete[24];
    unsigned char readable[8];
    unsigned char local_20[24];
    long local_50;
    long local_58;
    int local_64;
    int local_68;
    int local_6c;
    long var18;
    long var24;
    long var29;
    long var6;
    // x86-64 prologue: save rbp, frame 112 bytes
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileC2Ev)((char *)(&local_20[0]));
    local_50 = 0;
    if ((&local_20[0] != 0)) {
        local_50 = (long)((&local_20[0] + 8));
    }
    *(long *)(&writable[0]) = local_50;
    var6 = *(long *)(&writable[0]);
    local_58 = var6;
    *(long *)(&complete[0]) = ((var6 != 0) ? (local_58 - 8) : 0);
    *(long *)(&readable[0]) = *(long *)(&complete[0]);
    var18 = ((long (*)(void))(*(long *)((*(long *)(*(long *)(&writable[0])) + 16))))();
    var24 = ((long (*)(void))(*(long *)((*(long *)(*(long *)(&readable[0])) + 16))))();
    local_64 = var24;
    var29 = ((long (*)(void))(*(long *)((*(long *)(*(long *)(&writable[0])) + 24))))();
    local_68 = var29;
    local_6c = ((unsigned int)(local_64) + (unsigned int)(local_68));
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD2Ev)((char *)(&local_20[0]));
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_6c);
}
cpp_mi_dispatch fail 37 lines
// glaurung: cpp_mi_dispatch @ 0x1120
typedef struct Readable Readable;
typedef struct Writable Writable;
struct File;
__attribute__((no_stack_protector)) int32_t cpp_mi_dispatch(int32_t arg0, int32_t arg1) {
    extern void _ZN12_GLOBAL__N_14FileC2Ev(struct File *);
    extern void _ZN12_GLOBAL__N_14FileD2Ev(struct File *);
    unsigned char readable[8];
    unsigned char writable[8];
    int written;
    int got;
    unsigned char local_20[24];
    long local_50;
    int local_54;
    int local_58;
    int local_5c;
    long var12;
    long var19;
    // x86-64 prologue: save rbp, frame 96 bytes
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileC2Ev)((char *)(&local_20[0]));
    *(long *)(&readable[0]) = (long)((long)(&local_20[0]));
    local_50 = 0;
    if ((&local_20[0] != 0)) {
        local_50 = (long)((&local_20[0] + 8));
    }
    *(long *)(&writable[0]) = local_50;
    var12 = ((long (*)(void))(*(long *)((*(long *)(*(long *)(&writable[0])) + 16))))();
    local_54 = var12;
    written = local_54;
    var19 = ((long (*)(void))(*(long *)((*(long *)(*(long *)(&readable[0])) + 16))))();
    local_58 = var19;
    got = local_58;
    local_5c = ((written * 100) + got);
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD2Ev)((char *)(&local_20[0]));
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_5c);
}
cpp_mi_pointer_adjustment pass 30 lines
// glaurung: cpp_mi_pointer_adjustment @ 0x1270
typedef struct Readable Readable;
typedef struct Writable Writable;
struct File;
__attribute__((no_stack_protector)) int32_t cpp_mi_pointer_adjustment(int32_t arg0) {
    extern void _ZN12_GLOBAL__N_14FileC2Ev(struct File *);
    extern void _ZN12_GLOBAL__N_14FileD2Ev(struct File *);
    unsigned char readable[8];
    unsigned char writable[8];
    unsigned char local_20[24];
    long local_38;
    long local_40;
    long local_48;
    int local_4c;
    // x86-64 prologue: save rbp, frame 80 bytes
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileC2Ev)((char *)(&local_20[0]));
    *(int *)((&local_20[0] + 16)) = arg0;
    *(long *)(&readable[0]) = (long)((long)(&local_20[0]));
    local_48 = 0;
    if ((&local_20[0] != 0)) {
        local_48 = (long)((&local_20[0] + 8));
    }
    *(long *)(&writable[0]) = local_48;
    local_38 = *(long *)(&readable[0]);
    local_40 = *(long *)(&writable[0]);
    local_4c = ((local_38 != local_40) ? 1 : 0);
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD2Ev)((char *)(&local_20[0]));
    // x86-64 epilogue: restore rbp
    return (unsigned int)(local_4c);
}

gcc -O0

1/3
cpp_mi_cross_cast fail 48 lines
// glaurung: cpp_mi_cross_cast @ 0x14a3
typedef struct File File;
typedef struct Readable Readable;
typedef struct Writable Writable;
struct File;
int32_t cpp_mi_cross_cast(int32_t arg0, int32_t arg1) {
    extern __attribute__((noreturn)) long _Unwind_Resume(long);
    extern void _ZN12_GLOBAL__N_14FileC2Ev(const struct File *);
    extern void _ZN12_GLOBAL__N_14FileD1Ev(const struct File *);
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    Writable * writable;
    File * complete;
    Readable * readable;
    long local_18;
    unsigned char local_30[24];
    long ret;
    long var15;
    long var23;
    long var25;
    long var31;
    long var32;
    long var34;
    long var39;
    long var42;
    // x86-64 prologue: save rbp, frame 8 bytes
    local_18 = (long)(0x28);
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileC2Ev)((char *)(&local_30[0]));
    writable = (Writable *)((&local_30[0] + 8));
    complete = ((writable != 0) ? (File *)(((long)writable - 8)) : (File *)(0));
    readable = (Readable *)complete;
    var15 = ((long (*)(char *, long, long))(*(long *)((*(long *)((long)writable) + 16))))((char *)((long)writable), (unsigned long)((unsigned int)(arg0)), (unsigned long)((unsigned int)(arg1)));
    var23 = ((long (*)(char *, long))(*(long *)((*(long *)((long)readable) + 16))))((char *)((long)readable), (unsigned long)((unsigned int)(arg0)));
    var25 = (unsigned long)((unsigned int)(var23));
    var31 = ((long (*)(char *))(*(long *)((*(long *)((long)writable) + 24))))((char *)((long)writable));
    var32 = var31;
    var34 = (unsigned long)((unsigned int)((var25 + var31)));
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD1Ev)((char *)(&local_30[0]));
    ret = (unsigned long)((unsigned int)(var34));
    if ((local_18 == 0x28)) {
        // x86-64 epilogue: restore rbp
        return ret;
    }
    __stack_chk_fail();
    // __glaurung_eh_landing_1575
    var39 = var32;
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD1Ev)((char *)(&local_30[0]));
    var42 = _Unwind_Resume(var39);
}
cpp_mi_dispatch fail 45 lines
// glaurung: cpp_mi_dispatch @ 0x1338
typedef struct Readable Readable;
typedef struct Writable Writable;
struct File;
int32_t cpp_mi_dispatch(int32_t arg0, int32_t arg1) {
    extern __attribute__((noreturn)) long _Unwind_Resume(long);
    extern void _ZN12_GLOBAL__N_14FileC2Ev(const struct File *);
    extern void _ZN12_GLOBAL__N_14FileD1Ev(const struct File *);
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    Readable * readable;
    Writable * writable;
    int written;
    int got;
    long local_18;
    unsigned char local_30[24];
    long ret;
    long var13;
    long var21;
    long var22;
    long var26;
    long var31;
    long var34;
    // x86-64 prologue: save rbp, frame 8 bytes
    local_18 = (long)(0x28);
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileC2Ev)((char *)(&local_30[0]));
    readable = (Readable *)&local_30[0];
    writable = (Writable *)((&local_30[0] + 8));
    var13 = ((long (*)(char *, long, long))(*(long *)((*(long *)((long)writable) + 16))))((char *)((long)writable), (unsigned long)((unsigned int)(arg0)), (unsigned long)((unsigned int)(arg1)));
    written = var13;
    var21 = ((long (*)(char *, long))(*(long *)((*(long *)((long)readable) + 16))))((char *)((long)readable), (unsigned long)((unsigned int)(arg0)));
    var22 = var21;
    got = var21;
    var26 = (unsigned long)((unsigned int)((((unsigned long)((unsigned int)(written)) * 100) + (unsigned long)((unsigned int)(got)))));
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD1Ev)((char *)(&local_30[0]));
    ret = (unsigned long)((unsigned int)(var26));
    if ((local_18 == 0x28)) {
        // x86-64 epilogue: restore rbp
        return ret;
    }
    __stack_chk_fail();
    // __glaurung_eh_landing_13e7
    var31 = var22;
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD1Ev)((char *)(&local_30[0]));
    var34 = _Unwind_Resume(var31);
}
cpp_mi_pointer_adjustment pass 33 lines
// glaurung: cpp_mi_pointer_adjustment @ 0x1410
typedef struct Readable Readable;
typedef struct Writable Writable;
struct File;
int32_t cpp_mi_pointer_adjustment(int32_t arg0) {
    extern void _ZN12_GLOBAL__N_14FileC2Ev(const struct File *);
    extern void _ZN12_GLOBAL__N_14FileD1Ev(const struct File *);
    extern __attribute__((noreturn)) void __stack_chk_fail(void);
    const Readable * readable;
    const Writable * writable;
    char * first;
    char * second;
    long local_18;
    unsigned char local_30[24];
    long ret;
    int var11;
    // x86-64 prologue: save rbp, frame 8 bytes
    local_18 = (long)(0x28);
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileC2Ev)((char *)(&local_30[0]));
    *(int *)((&local_30[0] + 16)) = arg0;
    readable = (const Readable *)&local_30[0];
    writable = (const Writable *)((&local_30[0] + 8));
    first = (char *)readable;
    second = (char *)writable;
    var11 = ((first == second) ? 0 : 1);
    ((void (*)(char *))_ZN12_GLOBAL__N_14FileD1Ev)((char *)(&local_30[0]));
    ret = (unsigned long)((unsigned int)(var11));
    if ((local_18 != 0x28)) {
        __stack_chk_fail();
    }
    // x86-64 epilogue: restore rbp
    return ret;
}

clang -O2

3/3
cpp_mi_cross_cast pass 6 lines
// glaurung: cpp_mi_cross_cast @ 0x1120
int32_t cpp_mi_cross_cast(int32_t arg0, int32_t arg1) {
    int var2;
    var2 = ((unsigned int)((arg1 ^ arg0)) + arg0);
    return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(var2)) + (unsigned long)((unsigned int)(var2))))) + 1));
}
cpp_mi_dispatch pass 7 lines
// glaurung: cpp_mi_dispatch @ 0x1100
int32_t cpp_mi_dispatch(int32_t arg0, int32_t arg1) {
    int written;
    int got;
    written = (unsigned long)((unsigned int)((arg1 ^ arg0)));
    return (unsigned int)(((written * 100) + (unsigned long)((unsigned int)((written + (arg0 * 2))))));
}
cpp_mi_pointer_adjustment pass 4 lines
// glaurung: cpp_mi_pointer_adjustment @ 0x1110
int32_t cpp_mi_pointer_adjustment(int32_t arg0) {
    return 1;
}

gcc -O2

3/3
cpp_mi_cross_cast pass 6 lines
// glaurung: cpp_mi_cross_cast @ 0x1120
int32_t cpp_mi_cross_cast(int32_t arg0, int32_t arg1) {
    long var1;
    var1 = (unsigned long)((unsigned int)((arg1 ^ arg0)));
    return (unsigned int)(((var1 + (unsigned long)((unsigned int)((var1 + (arg0 * 2))))) + 1));
}
cpp_mi_dispatch pass 7 lines
// glaurung: cpp_mi_dispatch @ 0x1100
int32_t cpp_mi_dispatch(int32_t arg0, int32_t arg1) {
    int written;
    int got;
    written = (unsigned long)((unsigned int)((arg1 ^ arg0)));
    return (unsigned int)(((unsigned long)((unsigned int)((written + (arg0 * 2)))) + (written * 100)));
}
cpp_mi_pointer_adjustment pass 4 lines
// glaurung: cpp_mi_pointer_adjustment @ 0x1110
int32_t cpp_mi_pointer_adjustment(int32_t arg0) {
    return 1;
}

← 213 fixtures