Clang discards cases in a switch statement

Hello everyone,

I ran into a problem with Clang with a C program, where several of the cases in a switch statement were mapped to the default case (which is an abort in my example). This only happens when the function is inlined (so at least -O1) and started with Clang 13. Here is a (stripped-down) program on Godbolt’s Compiler Explorer, where cases 2, 4, 6 and 8 are mapped to the default case (and also the other cases don’t look quite right, I would expect a jmp after the call…): Compiler Explorer

Is there something (some kind of undefined behavior?) I’m missing or is this a bug in Clang?

With thanks and best regards,
Alex

typedef struct svalue_s svalue_t;

int get_type_1_hash(long int  val);
int get_type_3_hash(long int  val);
int get_type_5_hash(long int  val);
int get_type_7_hash(long int  val);

struct svalue_s
{
    short int type;
    union u {
        long int t1;
        long int t2;
        long int t3;
        long int t4;
        long int t5;
        long int t6;
        long int t7;
        long int t8;
    } u;
};

extern void fatal (const char *, ...) __attribute__((format (printf,1,2))) __attribute__((noreturn));

static inline int
svalue_hash (svalue_t *svp, int bits)
{
    long int result;

    switch (svp->type)
    {
        default:
            fatal("Illegal svalue type %d\n", svp->type);

        case 1:
            result = get_type_1_hash(svp->u.t1);
            break;

        case 2:
            result = svp->u.t2;
            break;

        case 3:
            result = get_type_3_hash(svp->u.t3);
            break;

        case 4:
            result = svp->u.t4;
            break;

        case 5:
            result = get_type_5_hash(svp->u.t5);
            break;

        case 6:
            result = svp->u.t6;
            break;

        case 7:
            result = get_type_7_hash(svp->u.t7);
            break;

        case 8:
            result = svp->u.t8;
            break;
    }

    return result & ((1<<bits) - 1);
}

typedef struct mapping_hash_s mapping_hash_t;
typedef struct map_chain_s map_chain_t;

struct mapping_hash_s {
    int mask;
    map_chain_t * chains[];
};

struct map_chain_s {
    map_chain_t * next;
    svalue_t data[];
};

svalue_t *get_map_lvalue (mapping_hash_t *hm, svalue_t *map_index)
{
    if (hm)
    {
        int idx = svalue_hash(map_index, 32) & hm->mask;
        return hm->chains[idx]->data;
    }

    return ((void*)0);
}

If bits is 32, which is the exact case that is being inlined, 1 << bits is UB. The even cases are therefore unconditional UB and so get removed, and the odd cases are UB if the corresponding hash function returns, so the jumps to the end of the block get removed.

1 Like

Ah, thank you very much. There is the UB I was looking for. 1UL << bits fixes everything.

I have a follow-up question: Clang does the same ‘optimization’ with (1U << bits), even though with unsigned operands the left-shift operator does not have an undefined behavior. Is there any additional UB I’m not getting?

Have you already tried to compile your exe with -fsanitize=undefined ? (see UndefinedBehaviorSanitizer — Clang 17.0.0git documentation) if you run a testcase that falls in such a case on -O0, it might tell you what UB you have.

For (1U << bits) with -fsanitize=undefined I get “shift exponent 32 is too large for 32-bit type ‘int’”. So why is it treating 1U as int, and not unsigned int?

I understand that it’s UB with int, but it should not be UB with unsigned int.

31 is UB for signed int. 32 is UB for signed and unsigned int as there are 32 bits in them.

Where is this coming from? The C standard says

The result of E1 << E2 is E1 left-shifted E2 bit positions; vacated bits are filled with zeros. If E1 has an unsigned type, the value of the result is E1 × 2^E2, reduced modulo one more than the maximum value representable in the result type. If E1 has a signed type and nonnegative value, and E1 × 2^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined.

So the standard clearly defines the behavior for unsigned types. Only for signed types there is undefined behavior if it exceeds the numeric range.

In the directly preceding paragraph:

If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined.

Ah, interesting. So (1U << 16) << 16 is well defined, while 1U << 32 (and also (1 << 16) << 16) is not. I can understand the argument for the signed type, but the unsigned one doesn’t seem intuitive. But anyway, that’s the standard. Thank you very much for the deep dive into undefined behavior of integer arithmetic and why it can crash your application.

With best regards,
Alex

1 Like