kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 9b07f8b452c18d654cb26c509ba4fc465504949a
parent 07644c78ebe68b366f0516109ee887de254b9c6e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 20 May 2026 19:58:03 -0700

c-frontend: integer-promote switch controlling expression

C99 6.8.4.2 requires the integer promotions on the switch controlling
expression. Without that, a signed char selector landed in a byte-sized
slot and was reloaded via unsigned byte load — losing the sign bit and
mismatching against negative case constants. Manifested only at runtime
(D/E paths); R is a structural roundtrip and hid it.

6_8_31_switch_char_extremes exercises -128..127 and pins the fix.

Diffstat:
Mlang/c/parse/parse_stmt.c | 13+++++++++++++
Atest/parse/cases/6_8_31_switch_char_extremes.c | 23+++++++++++++++++++++++
Atest/parse/cases/6_8_31_switch_char_extremes.expected | 1+
3 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -335,6 +335,19 @@ static void parse_switch_stmt(Parser* p) { vty = cg_top_type(p->cg); if (!vty) vty = type_prim(p->pool, TY_INT); if (!type_is_int(vty)) perr(p, "switch expression requires integer type"); + /* C99 6.8.4.2: the integer promotions are performed on the controlling + * expression. Without this, a `signed char` selector is stored to a + * byte-sized slot and reloaded with an unsigned-byte load, dropping the + * sign bit and miscomparing against negative case constants. */ + { + const Type* prom = type_unqual(p->pool, vty); + if (prom && prom->kind == TY_ENUM) prom = type_prim(p->pool, TY_INT); + else prom = type_promoted(p->pool, prom); + if (prom && prom != vty) { + cg_convert(p->cg, prom); + vty = prom; + } + } expect_punct(p, ')', "')' after switch expression"); memset(&ctx, 0, sizeof ctx); diff --git a/test/parse/cases/6_8_31_switch_char_extremes.c b/test/parse/cases/6_8_31_switch_char_extremes.c @@ -0,0 +1,23 @@ +/* signed char selector at the extremes of i8 range. A jump-table + * lowering that pre-promotes the selector to a wider type without + * preserving signedness would silently miscompile case INT8_MIN. */ +static int pick(signed char c) { + switch (c) { + case -128: return 1; + case -127: return 2; + case 0: return 3; + case 126: return 4; + case 127: return 5; + default: return 9; + } +} + +int test_main(void) { + int s = 0; + s += pick((signed char)-128); + s += pick((signed char)-127); + s += pick((signed char)0); + s += pick((signed char)127); + s += pick((signed char)42); /* miss -> 9 */ + return s - 15; /* 1+2+3+5+9 - 15 = 5 */ +} diff --git a/test/parse/cases/6_8_31_switch_char_extremes.expected b/test/parse/cases/6_8_31_switch_char_extremes.expected @@ -0,0 +1 @@ +5