Fixture 08
indirect dispatch
C · 4 functions · 4 lanes · 12 of 16 function-lanes behave identically
4 of 4 lanes have a function that returns a different result after decompilation: clang-O0 (3/4), clang-O2 (3/4), gcc-O0 (3/4), gcc-O2 (3/4).
Indirect-call / target-recovery fixture. A dispatcher selects a handler from an OPERATIONS TABLE (array of function pointers) indexed by a tag argument and returns the handler's result. Every handler returns a UNIQUE combination of its inputs, so an execution-differential test (original vs. recompiled decompilation) catches a mis-recovered call target the instant dispatch routes a tag to the wrong handler.
Targets review #8 (indirect / target-call recovery). The property under test: a DIRECT call must resolve to its named callee, while a genuinely INDIRECT call must stay an explicit indirect call whose computed target expression is a real table lookup (ops[tag]), never a fabricated/guessed direct callee. The table and all handlers live in this translation unit so it links standalone.
Differential vs. structural: - dispatch(), dispatch_switch(), tail_dispatch(): DIFFERENTIAL. Pure int functions with unique per-path constants; drivable by an int-diff gate. - apply(): STRUCTURAL. It takes a caller-supplied function pointer, so it cannot be driven by scalar ints alone; the assertion is that the callback parameter is preserved as an indirect call through the parameter, not inlined or bound to a fabricated callee.
Keep every handler pure (no globals, no memory) and deterministic.
/* 08_indirect_dispatch.c
*
* Indirect-call / target-recovery fixture. A dispatcher selects a handler from
* an OPERATIONS TABLE (array of function pointers) indexed by a tag argument and
* returns the handler's result. Every handler returns a UNIQUE combination of
* its inputs, so an execution-differential test (original vs. recompiled
* decompilation) catches a mis-recovered call target the instant `dispatch`
* routes a tag to the wrong handler.
*
* Targets review #8 (indirect / target-call recovery). The property under test:
* a DIRECT call must resolve to its named callee, while a genuinely INDIRECT
* call must stay an explicit indirect call whose computed target expression is a
* real table lookup (`ops[tag]`), never a fabricated/guessed direct callee. The
* table and all handlers live in this translation unit so it links standalone.
*
* Differential vs. structural:
* - dispatch(), dispatch_switch(), tail_dispatch(): DIFFERENTIAL. Pure int
* functions with unique per-path constants; drivable by an int-diff gate.
* - apply(): STRUCTURAL. It takes a caller-supplied function pointer, so it
* cannot be driven by scalar ints alone; the assertion is that the callback
* parameter is preserved as an indirect call through the parameter, not
* inlined or bound to a fabricated callee.
*
* Keep every handler pure (no globals, no memory) and deterministic.
*/
#include <stdint.h>
/* Function-pointer typedef for a binary integer handler. */
typedef int (*binop_fn)(int, int);
/* --- handlers: each returns a distinct combination of a and b ----------- */
static int h_add(int a, int b) { return a + b + 100; }
static int h_sub(int a, int b) { return a - b + 200; }
static int h_mul(int a, int b) { return a * b + 300; }
static int h_xor(int a, int b) { return (a ^ b) + 400; }
static int h_max(int a, int b) { return (a > b ? a : b) + 500; }
/* The OPERATIONS TABLE: an array of function pointers indexed by tag. A correct
* decompilation recovers `ops[tag]` as the indirect target expression. */
static binop_fn ops[5] = { h_add, h_sub, h_mul, h_xor, h_max };
/* Table-driven dispatch. `int dispatch(int tag, int a, int b)` selects a handler
* via an indirect call through ops[tag] and returns its result. The bounds guard
* returns a unique sentinel so an out-of-range tag stays deterministic. */
int dispatch(int tag, int a, int b) {
if (tag < 0 || tag >= 5)
return -1; /* unique out-of-range sentinel */
return ops[tag](a, b); /* genuine indirect call: target is ops[tag] */
}
/* The same selection expressed as a switch over direct calls. A compiler may or
* may not lower this to a jump table; either way each arm is a DIRECT call to a
* named callee, and the returns must match dispatch() arm-for-arm. */
int dispatch_switch(int tag, int a, int b) {
switch (tag) {
case 0: return h_add(a, b);
case 1: return h_sub(a, b);
case 2: return h_mul(a, b);
case 3: return h_xor(a, b);
case 4: return h_max(a, b);
default: return -1;
}
}
/* Indirect tail call: the selected handler's result is returned directly, giving
* the exact tail-call shape (jmp through a table slot) that a decompiler must
* render as an indirect call in return position, not as a fabricated direct one. */
int tail_dispatch(int tag, int a, int b) {
if (tag < 0 || tag >= 5)
return -1;
return ops[tag](a, b); /* tail position: often lowered to an indirect jmp */
}
/* Callback-parameter function. STRUCTURAL: `cb` is supplied by the caller, so
* this cannot be exercised by scalar ints through the differential gate. The
* assertion is that `cb(x)` stays an indirect call through the parameter. */
int apply(int (*cb)(int), int x) {
return cb(x) + 1;
} 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
3/4apply structural 8 lines
// glaurung: apply @ 0x1330
int apply(void * arg0, int arg1) {
long var1;
// x86-64 prologue: save rbp, frame 16 bytes
var1 = ((long (*)(long))((long)arg0))((unsigned long)((unsigned int)(arg1)));
// x86-64 epilogue: restore rbp
return (unsigned int)((var1 + 1));
} dispatch pass 32 lines
// glaurung: dispatch @ 0x1100
int dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int local_4;
long var3;
// x86-64 prologue: save rbp, frame 16 bytes
if (((long)(arg0) < 0)) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
if ((5 <= (long)(arg0))) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
var3 = ((long (*)(long, long))(ops[(long)(arg0)]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
local_4 = var3;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
} dispatch_switch pass 34 lines
// glaurung: dispatch_switch @ 0x1160
int dispatch_switch(int arg0, int arg1, int arg2) {
extern int h_add(int, int);
extern int h_max(int, int);
extern int h_mul(int, int);
extern int h_sub(int, int);
extern int h_xor(int, int);
long var10;
long var12;
long var14;
long var6;
long var8;
// x86-64 prologue: save rbp, frame 32 bytes
switch ((unsigned int)(arg0)) {
case 0:
var6 = ((long (*)(long, long))h_add)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return (unsigned int)(var6);
case 1:
var8 = ((long (*)(long, long))h_sub)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return (unsigned int)(var8);
case 2:
var10 = ((long (*)(long, long))h_mul)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return (unsigned int)(var10);
case 3:
var12 = ((long (*)(long, long))h_xor)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return (unsigned int)(var12);
case 4:
var14 = ((long (*)(long, long))h_max)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return (unsigned int)(var14);
default:
return (unsigned int)(-1);
}
// x86-64 epilogue: restore rbp
} tail_dispatch pass 32 lines
// glaurung: tail_dispatch @ 0x12d0
int tail_dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int local_4;
long var3;
// x86-64 prologue: save rbp, frame 16 bytes
if (((long)(arg0) < 0)) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
if ((5 <= (long)(arg0))) {
local_4 = -1;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
}
var3 = ((long (*)(long, long))(ops[(long)(arg0)]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
local_4 = var3;
// x86-64 epilogue: restore rbp
return (unsigned int)(local_4);
} clang -O2
3/4apply structural 13 lines
// glaurung: apply @ 0x11f0
int apply(void * arg0, int arg1) {
long local_8;
long ret;
long var1;
int var3;
local_8 = ret;
var1 = ((long (*)(long))((long)arg0))((unsigned long)((unsigned int)(arg1)));
var3 = (var1 + 1);
ret = (unsigned long)((unsigned int)(var3));
// x86-64 epilogue: tear down frame
return (unsigned int)(var3);
} dispatch pass 21 lines
// glaurung: dispatch @ 0x1100
int dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int ret;
if (((unsigned long)((unsigned long)((unsigned int)(arg0))) <= (unsigned long)(4))) {
ret = ((int (*)(long, long))(ops[(unsigned long)((unsigned int)(arg0))]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return ret;
}
return 0xffffffff;
} dispatch_switch pass 17 lines
// glaurung: dispatch_switch @ 0x1120
int dispatch_switch(int arg0, int arg1, int arg2) {
switch ((unsigned long)((unsigned int)(arg0))) {
case 0:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) + arg2))) + 100));
case 1:
return (unsigned int)(((unsigned long)((unsigned int)(((unsigned long)((unsigned int)(arg1)) - arg2))) + 200));
case 2:
return (unsigned int)(((unsigned long)((unsigned int)((arg2 * (unsigned long)((unsigned int)(arg1))))) + 300));
case 3:
return (unsigned int)(((unsigned long)((unsigned int)((arg2 ^ (unsigned long)((unsigned int)(arg1))))) + 400));
case 4:
return (unsigned int)(((((((unsigned int)(arg1) == (unsigned int)(arg2)) | (arg1 < arg2)) == 0) ? (unsigned long)((unsigned int)(arg1)) : arg2) + 500));
default:
return 0xffffffff;
}
} tail_dispatch pass 21 lines
// glaurung: tail_dispatch @ 0x11d0
int tail_dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int ret;
if (((unsigned long)((unsigned long)((unsigned int)(arg0))) <= (unsigned long)(4))) {
ret = ((int (*)(long, long))(ops[(unsigned long)((unsigned int)(arg0))]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return ret;
}
return 0xffffffff;
} gcc -O0
3/4apply structural 8 lines
// glaurung: apply @ 0x12bd
int apply(void * arg0, int arg1) {
long var2;
// x86-64 prologue: save rbp, frame 16 bytes
var2 = ((long (*)(long))((long)arg0))((unsigned long)((unsigned int)(arg1)));
// x86-64 epilogue: restore rbp
return (unsigned int)((var2 + 1));
} dispatch pass 28 lines
// glaurung: dispatch @ 0x1186
int dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int ret;
// x86-64 prologue: save rbp, frame 16 bytes
if (((long)(arg0) < 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
if (((((unsigned long)((unsigned int)(arg0)) == 4) | ((long)(arg0) < 4)) == 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
ret = ((int (*)(long, long))(ops[(long)((int)((unsigned long)((unsigned int)(arg0))))]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
// x86-64 epilogue: restore rbp
return ret;
} dispatch_switch pass 34 lines
// glaurung: dispatch_switch @ 0x11d4
int dispatch_switch(int arg0, int arg1, int arg2) {
extern int h_add(int, int);
extern int h_max(int, int);
extern int h_mul(int, int);
extern int h_sub(int, int);
extern int h_xor(int, int);
long var12;
long var15;
long var18;
long var21;
long var9;
// x86-64 prologue: save rbp, frame 16 bytes
switch ((unsigned long)((unsigned int)(arg0))) {
case 0:
var9 = ((long (*)(long, long))h_add)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return var9;
case 1:
var12 = ((long (*)(long, long))h_sub)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return var12;
case 2:
var15 = ((long (*)(long, long))h_mul)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return var15;
case 3:
var18 = ((long (*)(long, long))h_xor)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return var18;
case 4:
var21 = ((long (*)(long, long))h_max)((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
return var21;
default:
return 0xffffffff;
}
// x86-64 epilogue: restore rbp
} tail_dispatch pass 28 lines
// glaurung: tail_dispatch @ 0x126f
int tail_dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int ret;
// x86-64 prologue: save rbp, frame 16 bytes
if (((long)(arg0) < 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
if (((((unsigned long)((unsigned int)(arg0)) == 4) | ((long)(arg0) < 4)) == 0)) {
// x86-64 epilogue: restore rbp
return 0xffffffff;
}
ret = ((int (*)(long, long))(ops[(long)((int)((unsigned long)((unsigned int)(arg0))))]))((unsigned long)((unsigned int)(arg1)), (unsigned long)((unsigned int)(arg2)));
// x86-64 epilogue: restore rbp
return ret;
} gcc -O2
3/4apply structural 6 lines
// glaurung: apply @ 0x1230
int apply(void * arg0, int arg1) {
long var1;
var1 = ((long (*)(long))((long)arg0))((unsigned long)((unsigned int)(arg1)));
return (unsigned int)((var1 + 1));
} dispatch pass 27 lines
// glaurung: dispatch @ 0x1160
int dispatch(int arg0, int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
int ret;
long var0;
long var1;
long var2;
var0 = (long)(arg0);
var1 = (unsigned long)((unsigned int)(arg1));
var2 = (unsigned long)((unsigned int)(arg2));
if (((unsigned long)((unsigned long)((unsigned int)(arg0))) <= (unsigned long)(4))) {
ret = ((int (*)(long, long))(ops[var0]))(var1, var2);
return ret;
}
return 0xffffffff;
} dispatch_switch pass 17 lines
// glaurung: dispatch_switch @ 0x1190
int dispatch_switch(int arg0, int arg1, int arg2) {
switch ((unsigned long)((unsigned int)(arg0))) {
case 0:
return (unsigned int)(((arg1 + arg2) + 100));
case 1:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 - arg2))) + 200));
case 2:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 * arg2))) + 300));
case 3:
return (unsigned int)(((unsigned long)((unsigned int)((arg1 ^ arg2))) + 400));
case 4:
return (unsigned int)((((arg1 < arg2) ? arg2 : arg1) + 500));
default:
return (unsigned int)(-1);
}
} tail_dispatch pass 27 lines
// glaurung: tail_dispatch @ 0x1200
long tail_dispatch(unsigned int arg0, unsigned int arg1, int arg2) {
extern void h_add(void);
extern void h_max(void);
extern void h_mul(void);
extern void h_sub(void);
extern void h_xor(void);
static void (*ops[5])(void) = {
(void (*)(void))h_add,
(void (*)(void))h_sub,
(void (*)(void))h_mul,
(void (*)(void))h_xor,
(void (*)(void))h_max,
};
long ret;
long var0;
long var1;
long var2;
var0 = (long)((int)(arg0));
var1 = (unsigned long)(arg1);
var2 = (unsigned long)((unsigned int)(arg2));
if (((unsigned long)(arg0) <= (unsigned long)(4))) {
ret = ((long (*)(long, long))(ops[var0]))(var1, var2);
return ret;
}
return 0xffffffff;
}