// Phase 6.2 UBSan-min smoke probe.
//
// Nine UB cases — one per recoverable handler kind we exercise:
//   kind 0 (sentinel 0xC0DE):  add-overflow             (i16 INT_MAX + 1)
//   kind 1 (sentinel 0xC0DF):  shift-out-of-bounds      (1 << 17 on a u16)
//   kind 2 (sentinel 0xC0E0):  divrem-overflow          (n / 0)
//   kind 3 (sentinel 0xC0E1):  sub-overflow             (INT_MIN - 1)
//   kind 4 (sentinel 0xC0E2):  mul-overflow             (INT_MAX * 2)
//   kind 5 (sentinel 0xC0E3):  negate-overflow          (-INT_MIN)
//   kind 6 (sentinel 0xC0E4):  pointer-overflow         (ptr + huge offset)
//   kind 7 (sentinel 0xC0E5):  load-invalid-value       (_Bool from byte=2)
//   kind 8 (sentinel 0xC0E6):  out-of-bounds            (arr[idx>=N])
//
// The probe overrides each relevant `__ubsan_handle_*_minimal` recovering
// handler with a strong definition that records its firing in a static
// state byte.  After each UB, the probe writes 0xC0DE+kind to a per-kind
// 16-bit slot at 0x025000+kind*2 to prove (a) the instrumentation fired
// and (b) execution recovered cleanly past the UB.  The recover handler
// returning normally is the whole point of -fsanitize-minimal-runtime
// + -fsanitize-recover; this probe is what proves the round-trip.
//
// To verify all nine at once we cascade the sentinel writes through a
// staircase of word stores so the smoke harness can read independent
// 16-bit values back from MAME.
//
// Compile with -fsanitize=undefined -fsanitize-minimal-runtime.

#include <stdint.h>


// Bank-2 BSS at $025000-$025014 -- outside the SHR shadow and outside
// $C000-$CFFF IO window.  link816 places .bss at the user-specified
// --bss-base (we pass 0xA000) so these constant addresses are
// independent of BSS layout.
#define MARK_ADD_OVF      ((volatile uint16_t *)0x025000UL)
#define MARK_SHIFT_OOB    ((volatile uint16_t *)0x025002UL)
#define MARK_DIV_ZERO     ((volatile uint16_t *)0x025004UL)
#define MARK_SUB_OVF      ((volatile uint16_t *)0x025006UL)
#define MARK_MUL_OVF      ((volatile uint16_t *)0x025008UL)
#define MARK_NEG_OVF      ((volatile uint16_t *)0x02500AUL)
#define MARK_PTR_OVF      ((volatile uint16_t *)0x02500CUL)
#define MARK_LOAD_INVAL   ((volatile uint16_t *)0x02500EUL)
#define MARK_OUT_OF_BNDS  ((volatile uint16_t *)0x025010UL)
#define DONE_SENTINEL     ((volatile uint16_t *)0x025012UL)


// Strong overrides win over runtime/ubsan.o's weak-by-link defaults.
// Each fires once per kind and records that the corresponding UB
// instrumentation reached us.  Recovering handlers MUST return so the
// probe continues executing past the UB site.
static volatile uint8_t handlerFiredAdd      = 0;
static volatile uint8_t handlerFiredShift    = 0;
static volatile uint8_t handlerFiredDiv      = 0;
static volatile uint8_t handlerFiredSub      = 0;
static volatile uint8_t handlerFiredMul      = 0;
static volatile uint8_t handlerFiredNeg      = 0;
static volatile uint8_t handlerFiredPtr      = 0;
static volatile uint8_t handlerFiredLoadInv  = 0;
static volatile uint8_t handlerFiredOob      = 0;


void __ubsan_handle_add_overflow_minimal(void) {
    handlerFiredAdd = 1;
}


void __ubsan_handle_shift_out_of_bounds_minimal(void) {
    handlerFiredShift = 1;
}


void __ubsan_handle_divrem_overflow_minimal(void) {
    handlerFiredDiv = 1;
}


void __ubsan_handle_sub_overflow_minimal(void) {
    handlerFiredSub = 1;
}


void __ubsan_handle_mul_overflow_minimal(void) {
    handlerFiredMul = 1;
}


void __ubsan_handle_negate_overflow_minimal(void) {
    handlerFiredNeg = 1;
}


void __ubsan_handle_pointer_overflow_minimal(void) {
    handlerFiredPtr = 1;
}


void __ubsan_handle_load_invalid_value_minimal(void) {
    handlerFiredLoadInv = 1;
}


void __ubsan_handle_out_of_bounds_minimal(void) {
    handlerFiredOob = 1;
}


// Each UB site goes through a noinline wrapper so the optimizer
// cannot constant-fold the operation away.  __attribute__((noinline))
// + volatile inputs blocks the obvious folding paths; we also wrap
// the inputs with `volatile` reads so the LLVM mid-end has no
// known-value to work with.
__attribute__((noinline))
static int16_t triggerAddOverflow(int16_t a, int16_t b) {
    return a + b;
}


__attribute__((noinline))
static uint16_t triggerShiftOob(uint16_t a, uint16_t s) {
    return a << s;
}


__attribute__((noinline))
static int16_t triggerDivByZero(int16_t a, int16_t b) {
    return a / b;
}


__attribute__((noinline))
static int16_t triggerSubOverflow(int16_t a, int16_t b) {
    return a - b;
}


__attribute__((noinline))
static int16_t triggerMulOverflow(int16_t a, int16_t b) {
    return a * b;
}


__attribute__((noinline))
static int16_t triggerNegateOverflow(int16_t a) {
    return -a;
}


__attribute__((noinline))
static char *triggerPointerOverflow(char *p, int32_t o) {
    return p + o;
}


__attribute__((noinline))
static int triggerLoadInvalidValue(volatile uint8_t *p) {
    _Bool v = *(_Bool *)p;
    // Use the value so the load isn't dead-stripped.  We don't trust
    // the post-instrumentation cast to a 0/1 narrow value -- the
    // important thing is the load itself fired the handler.
    return v ? 1 : 0;
}


__attribute__((noinline))
static int16_t triggerOutOfBounds(int16_t idx) {
    static int16_t arr[4] = { 10, 20, 30, 40 };
    return arr[idx];
}


int main(void) {
    // --- case 0: signed-overflow add (INT16_MAX + 1) ---
    volatile int16_t aMax = 0x7FFF;
    volatile int16_t aOne = 1;
    (void)triggerAddOverflow(aMax, aOne);
    if (handlerFiredAdd) {
        *MARK_ADD_OVF = 0xC0DE;
    }

    // --- case 1: shift-out-of-bounds (1 << 17 on a u16) ---
    volatile uint16_t base = 1;
    volatile uint16_t shf  = 17;
    (void)triggerShiftOob(base, shf);
    if (handlerFiredShift) {
        *MARK_SHIFT_OOB = 0xC0DF;
    }

    // --- case 2: divide-by-zero (signed) ---
    volatile int16_t num = 42;
    volatile int16_t den = 0;
    (void)triggerDivByZero(num, den);
    if (handlerFiredDiv) {
        *MARK_DIV_ZERO = 0xC0E0;
    }

    // --- case 3: sub-overflow (INT16_MIN - 1) ---
    volatile int16_t aMin = (int16_t)0x8000;
    (void)triggerSubOverflow(aMin, aOne);
    if (handlerFiredSub) {
        *MARK_SUB_OVF = 0xC0E1;
    }

    // --- case 4: mul-overflow (INT16_MAX * 2 wraps) ---
    volatile int16_t aTwo = 2;
    (void)triggerMulOverflow(aMax, aTwo);
    if (handlerFiredMul) {
        *MARK_MUL_OVF = 0xC0E2;
    }

    // --- case 5: negate-overflow (-INT16_MIN) ---
    (void)triggerNegateOverflow(aMin);
    if (handlerFiredNeg) {
        *MARK_NEG_OVF = 0xC0E3;
    }

    // --- case 6: pointer-overflow (signed-wrap on i16 addr) ---
    // Cast a high address to char* and add a positive offset that
    // overflows the address calculation.  -fsanitize=pointer-overflow
    // fires on signed-overflow of the offset add.
    volatile uint32_t hiAddr = 0xFFFFFFF0UL;
    volatile int32_t  big    = 0x40;
    char *p = (char *)(uintptr_t)hiAddr;
    (void)triggerPointerOverflow(p, big);
    if (handlerFiredPtr) {
        *MARK_PTR_OVF = 0xC0E4;
    }

    // --- case 7: load-invalid-value (_Bool from byte=2) ---
    volatile uint8_t boolByte = 2;
    (void)triggerLoadInvalidValue(&boolByte);
    if (handlerFiredLoadInv) {
        *MARK_LOAD_INVAL = 0xC0E5;
    }

    // --- case 8: out-of-bounds (static arr[idx>=N]) ---
    volatile int16_t badIdx = 7;
    (void)triggerOutOfBounds(badIdx);
    if (handlerFiredOob) {
        *MARK_OUT_OF_BNDS = 0xC0E6;
    }

    // Final liveness sentinel -- only written if we got past all nine
    // UB sites without the runtime aborting (which would have spun on
    // a BRK_pseudo at $70 instead of reaching here).
    *DONE_SENTINEL = 0xC0DA;

    // Halt -- crt0's return-from-main path hits a BRK that headless
    // MAME wild-jumps from, so spin-wait instead.
    while (1) {
    }
}
