kit

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

commit 679337e910a27991bdf2665e6986afe130c607a0
parent 6fa432ae8271b8fb61030d2832724f887adf102e
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 11:28:39 -0700

Validate CG memory access mismatches

Diffstat:
Mlang/toy/expr.c | 6++++--
Mlang/toy/parser.c | 39+++++++--------------------------------
Msrc/api/cg.c | 206++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mtest/api/cg_type_test.c | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 322 insertions(+), 50 deletions(-)

diff --git a/lang/toy/expr.c b/lang/toy/expr.c @@ -36,7 +36,8 @@ CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name) { ToyVar* v = toy_find_var(p, name); if (v) { toy_push_var_lvalue(p, v); - cfree_cg_load(p->cg, toy_mem_access(p, v->type)); + if (cfree_cg_type_kind(p->c, v->type) != CFREE_CG_TYPE_RECORD) + cfree_cg_load(p->cg, toy_mem_access(p, v->type)); p->last_type = v->toy_type; return v->type; } @@ -44,7 +45,8 @@ CfreeCgTypeId toy_push_named_rvalue(ToyParser* p, CfreeSym name) { ToyGlobal* g = toy_find_global(p, name); if (g) { cfree_cg_push_symbol_lvalue(p->cg, g->sym, 0); - cfree_cg_load(p->cg, toy_mem_access(p, g->type)); + if (cfree_cg_type_kind(p->c, g->type) != CFREE_CG_TYPE_RECORD) + cfree_cg_load(p->cg, toy_mem_access(p, g->type)); p->last_type = g->toy_type; return g->type; } diff --git a/lang/toy/parser.c b/lang/toy/parser.c @@ -86,34 +86,6 @@ static int toy_records_have_matching_storage(ToyParser* p, return 1; } -static int toy_store_record_value_as(ToyParser* p, CfreeCgTypeId src_ty, - CfreeCgLocal dst_slot, - CfreeCgTypeId dst_ty) { - CfreeCgLocal src_slot; - uint32_t i; - uint32_t nfields; - if (!toy_records_have_matching_storage(p, dst_ty, src_ty)) { - toy_error(p, p->cur.loc, "record storage mismatch"); - return 0; - } - src_slot = cfree_cg_local(p->cg, src_ty, toy_slot_attrs(0)); - cfree_cg_push_local(p->cg, src_slot); - cfree_cg_swap(p->cg); - cfree_cg_store(p->cg, toy_mem_access(p, src_ty)); - nfields = cfree_cg_type_record_nfields(p->c, dst_ty); - for (i = 0; i < nfields; ++i) { - CfreeCgField field; - if (cfree_cg_type_record_field(p->c, dst_ty, i, &field, NULL)) return 0; - cfree_cg_push_local(p->cg, dst_slot); - cfree_cg_field(p->cg, i); - cfree_cg_push_local(p->cg, src_slot); - cfree_cg_field(p->cg, i); - cfree_cg_load(p->cg, toy_mem_access(p, field.type)); - cfree_cg_store(p->cg, toy_mem_access(p, field.type)); - } - return 1; -} - static int toy_copy_record_lvalue_to_local(ToyParser* p, CfreeCgTypeId src_ty, CfreeCgLocal dst_slot, CfreeCgTypeId dst_ty) { @@ -925,8 +897,9 @@ static int toy_parse_let_stmt(ToyParser* p) { if (has_init) { if (toy_type_is_slice(p, init_toy_type)) { if (!toy_copy_record_lvalue_to_local(p, init_ty, slot, ty)) return 0; - } else if (copy_record_init) { - if (!toy_store_record_value_as(p, init_ty, slot, ty)) return 0; + } else if (copy_record_init || + cfree_cg_type_kind(p->c, ty) == CFREE_CG_TYPE_RECORD) { + if (!toy_copy_record_lvalue_to_local(p, init_ty, slot, ty)) return 0; } else { cfree_cg_push_local(p->cg, slot); cfree_cg_swap(p->cg); @@ -1633,7 +1606,8 @@ static int toy_parse_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "type mismatch in assignment"); return 0; } - if (toy_type_is_slice(p, v->toy_type)) { + if (toy_type_is_slice(p, v->toy_type) || + cfree_cg_type_kind(p->c, v->type) == CFREE_CG_TYPE_RECORD) { if (!toy_copy_record_lvalue_to_var(p, expr_ty, v, NULL)) return 0; if (!toy_parser_expect(p, TOK_SEMI)) { toy_error(p, p->cur.loc, "expected ';' after assignment"); @@ -1659,7 +1633,8 @@ static int toy_parse_stmt(ToyParser* p) { toy_error(p, p->cur.loc, "type mismatch in assignment"); return 0; } - if (toy_type_is_slice(p, g->toy_type)) { + if (toy_type_is_slice(p, g->toy_type) || + cfree_cg_type_kind(p->c, g->type) == CFREE_CG_TYPE_RECORD) { if (!toy_copy_record_lvalue_to_var(p, expr_ty, NULL, g)) return 0; if (!toy_parser_expect(p, TOK_SEMI)) { toy_error(p, p->cur.loc, "expected ';' after assignment"); diff --git a/src/api/cg.c b/src/api/cg.c @@ -1510,6 +1510,7 @@ static ApiSValue *api_pick_victim(CfreeCg *g, u8 cls) { } static MemAccess api_mem_for_spill(CfreeCg *g, const ApiSValue *sv); +static u8 api_type_class(CfreeCgTypeId ty); static void api_regalloc_begin(CfreeCg *g) { CGTarget *T = g->target; @@ -1623,6 +1624,76 @@ static MemAccess api_mem_from_access(CfreeCg *g, const Operand *lv, return m; } +static CfreeCgTypeId api_mem_access_type(CfreeCg *g, CfreeCgMemAccess access, + CfreeCgTypeId fallback, + const char *who) { + CfreeCgTypeId ty = resolve_type(g->c, access.type); + if (!ty) + ty = resolve_type(g->c, fallback); + if (!ty) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: %s has no value type", who); + } + return ty; +} + +static u32 api_mem_type_size(CfreeCg *g, CfreeCgTypeId ty, const char *who) { + ty = resolve_type(g->c, ty); + if (!ty) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: %s has invalid type", who); + } + if (cg_type_is_void(g->c, ty)) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: %s uses void type", who); + } + return abi_cg_sizeof(g->c->abi, ty); +} + +static void api_require_scalar_mem_type(CfreeCg *g, const char *who, + CfreeCgTypeId ty) { + if (cg_type_is_aggregate(g->c, ty)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: %s cannot use aggregate value type (size %u); " + "copy fields or use byte memory operations", + who, (unsigned)api_mem_type_size(g, ty, who)); + } + (void)api_mem_type_size(g, ty, who); +} + +static void api_require_pointer_value(CfreeCg *g, const char *who, + CfreeCgTypeId ty) { + if (!cg_type_pointee(g->c, ty)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: %s operand must be a pointer", who); + } +} + +static void api_validate_memory_value(CfreeCg *g, const char *who, + CfreeCgTypeId access_ty, + CfreeCgTypeId value_ty) { + u32 access_size; + u32 value_size; + access_ty = resolve_type(g->c, access_ty); + value_ty = resolve_type(g->c, value_ty); + api_require_scalar_mem_type(g, who, access_ty); + if (!value_ty) { + compiler_panic(g->c, g->cur_loc, "CfreeCg: %s value has no type", who); + } + if (cg_type_is_aggregate(g->c, value_ty)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: %s value is aggregate (size %u); copy fields or " + "use byte memory operations", + who, (unsigned)api_mem_type_size(g, value_ty, who)); + } + access_size = api_mem_type_size(g, access_ty, who); + value_size = api_mem_type_size(g, value_ty, who); + if (access_size != value_size || + api_type_class(access_ty) != api_type_class(value_ty)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: %s value type/size mismatch: access size %u, " + "value size %u", + who, (unsigned)access_size, (unsigned)value_size); + } +} + static MemAccess api_mem_for_spill(CfreeCg *g, const ApiSValue *sv) { CfreeCgTypeId ty = api_owned_reg_type(g, sv); MemAccess m; @@ -3409,6 +3480,27 @@ void cfree_cg_load(CfreeCg *g, CfreeCgMemAccess access) { api_push(g, v); return; } + ty = api_mem_access_type(g, access, api_sv_type(&v), "load"); + if (cg_type_is_aggregate(g->c, api_sv_type(&v))) { + u32 access_size; + u32 lvalue_size; + if (!cg_type_is_aggregate(g->c, ty)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: load scalar access from aggregate lvalue " + "requires selecting a field"); + } + access_size = api_mem_type_size(g, ty, "load"); + lvalue_size = api_mem_type_size(g, api_sv_type(&v), "load"); + if (access_size != lvalue_size) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: load aggregate type/size mismatch: access size " + "%u, lvalue size %u", + (unsigned)access_size, (unsigned)lvalue_size); + } + api_push(g, v); + return; + } + api_require_scalar_mem_type(g, "load", ty); if (v.source_local != CFREE_CG_LOCAL_NONE && api_local_const_load(g, v.source_local, access, &dst)) { api_release(g, &v); @@ -3416,9 +3508,6 @@ void cfree_cg_load(CfreeCg *g, CfreeCgMemAccess access) { return; } api_ensure_reg(g, &v); - ty = resolve_type(g->c, access.type); - if (!ty) - ty = api_sv_type(&v); if (v.source_local != CFREE_CG_LOCAL_NONE && v.op.kind == OPK_REG) { dst = v.op; dst.type = ty; @@ -3471,6 +3560,7 @@ void cfree_cg_store(CfreeCg *g, CfreeCgMemAccess access) { CGTarget *T; CfreeCgTypeId ty; Operand src; + int scalar_aggregate_store = 0; if (!g) return; if (access.flags & CFREE_CG_MEM_VOLATILE) @@ -3478,16 +3568,75 @@ void cfree_cg_store(CfreeCg *g, CfreeCgMemAccess access) { T = g->target; rv = api_pop(g); lv = api_pop(g); - api_ensure_reg(g, &lv); - api_ensure_reg(g, &rv); if (!api_is_lvalue_sv(&lv)) { compiler_panic(g->c, g->cur_loc, "CfreeCg: store destination is not an lvalue"); return; } - ty = resolve_type(g->c, access.type); - if (!ty) - ty = api_sv_type(&lv); + ty = api_mem_access_type(g, access, api_sv_type(&lv), "store"); + if (cg_type_is_aggregate(g->c, api_sv_type(&lv)) && + !cg_type_is_aggregate(g->c, api_sv_type(&rv)) && + !cg_type_is_aggregate(g->c, ty)) { + u32 access_size = api_mem_type_size(g, ty, "store"); + u32 dst_size = api_mem_type_size(g, api_sv_type(&lv), "store"); + u32 value_size = api_mem_type_size(g, api_sv_type(&rv), "store"); + if (access_size != dst_size || value_size != dst_size) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: store scalar/aggregate size mismatch: access " + "size %u, destination size %u, value size %u", + (unsigned)access_size, (unsigned)dst_size, + (unsigned)value_size); + } + scalar_aggregate_store = 1; + } + if (!scalar_aggregate_store && + (cg_type_is_aggregate(g->c, ty) || + cg_type_is_aggregate(g->c, api_sv_type(&lv)) || + cg_type_is_aggregate(g->c, api_sv_type(&rv)))) { + CfreeCgTypeId ptr_ty; + Operand dst_addr, src_addr; + AggregateAccess agg; + u32 dst_size = api_mem_type_size(g, api_sv_type(&lv), "store"); + u32 src_size = api_mem_type_size(g, api_sv_type(&rv), "store"); + u32 access_size = + cg_type_is_aggregate(g->c, ty) ? api_mem_type_size(g, ty, "store") + : dst_size; + if (!api_is_lvalue_sv(&rv)) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: aggregate store source is not an lvalue"); + } + if (!cg_type_is_aggregate(g->c, api_sv_type(&lv)) || + !cg_type_is_aggregate(g->c, api_sv_type(&rv)) || + access_size != dst_size || access_size != src_size) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: store aggregate type/size mismatch: access " + "size %u, destination size %u, value size %u", + (unsigned)access_size, (unsigned)dst_size, + (unsigned)src_size); + } + if (lv.source_local != CFREE_CG_LOCAL_NONE) { + api_local_const_clear(api_local_from_handle(g, lv.source_local)); + } else if (lv.op.kind == OPK_INDIRECT || lv.op.kind == OPK_GLOBAL || + (access.flags & CFREE_CG_MEM_VOLATILE)) { + api_local_const_memory_boundary(g); + } + ptr_ty = cg_type_ptr_to(g->c, api_sv_type(&lv)); + dst_addr = api_lvalue_addr(g, &lv, ptr_ty); + src_addr = api_lvalue_addr(g, &rv, ptr_ty); + memset(&agg, 0, sizeof agg); + agg.size = access_size; + agg.align = access.align ? access.align + : abi_cg_alignof(g->c->abi, api_sv_type(&lv)); + T->copy_bytes(T, dst_addr, src_addr, agg); + api_free_reg(g, dst_addr.v.reg, RC_INT); + api_free_reg(g, src_addr.v.reg, RC_INT); + api_release(g, &lv); + api_release(g, &rv); + return; + } + api_validate_memory_value(g, "store", ty, api_sv_type(&rv)); + api_ensure_reg(g, &lv); + api_ensure_reg(g, &rv); if (api_sv_op_is_reg_or_imm(&rv)) { src = rv.op; } else { @@ -3993,10 +4142,11 @@ void cfree_cg_intrinsic(CfreeCg *g, CfreeCgIntrinsic intrin, uint32_t nargs, if (api_intrinsic_is_overflow(intrin)) { CfreeCgTypeId vty = rty ? rty : (nargs ? api_sv_type(&svs[0]) : int_ty); + CfreeCgTypeId bool_ty = builtin_id(CFREE_CG_BUILTIN_BOOL); Reg rr = api_alloc_reg_or_spill(g, api_type_class(vty), vty); - Reg ok = api_alloc_reg_or_spill(g, RC_INT, int_ty); + Reg ok = api_alloc_reg_or_spill(g, RC_INT, bool_ty); dsts[0] = api_op_reg(rr, vty); - dsts[1] = api_op_reg(ok, int_ty); + dsts[1] = api_op_reg(ok, bool_ty); ndst = 2; } else if (!api_intrinsic_is_void(intrin) && !cg_type_is_void(g->c, rty)) { Reg rr = api_alloc_reg_or_spill(g, api_type_class(rty), rty); @@ -4015,7 +4165,7 @@ void cfree_cg_intrinsic(CfreeCg *g, CfreeCgIntrinsic intrin, uint32_t nargs, if (api_intrinsic_is_overflow(intrin)) { api_push(g, api_make_sv(dsts[0], dsts[0].type)); - api_push(g, api_make_sv(dsts[1], int_ty)); + api_push(g, api_make_sv(dsts[1], dsts[1].type)); } else if (ndst == 1) { api_push(g, api_make_sv(dsts[0], rty)); } @@ -4037,6 +4187,11 @@ static CfreeCgTypeId api_atomic_pointee(CfreeCg *g, CfreeCgTypeId pty, static MemAccess api_mem_for_atomic(CfreeCg *g, CfreeCgTypeId val_ty) { MemAccess ma; + api_require_scalar_mem_type(g, "atomic memory access", val_ty); + if (api_mem_type_size(g, val_ty, "atomic memory access") > 8u) { + compiler_panic(g->c, g->cur_loc, + "CfreeCg: atomic memory access size exceeds 8 bytes"); + } memset(&ma, 0, sizeof ma); ma.type = val_ty; ma.size = val_ty ? abi_cg_sizeof(g->c->abi, val_ty) : 0; @@ -4052,6 +4207,8 @@ int cfree_cg_atomic_is_legal(CfreeCompiler *c, CfreeCgMemAccess access, (void)order; if (!ty) return 0; + if (cg_type_is_aggregate(c, ty) || cg_type_is_void(c, ty)) + return 0; return abi_cg_sizeof(c->abi, access.type) <= 8; } @@ -4059,6 +4216,8 @@ int cfree_cg_atomic_is_lock_free(CfreeCompiler *c, CfreeCgMemAccess access) { CfreeCgTypeId ty = resolve_type(c, access.type); if (!ty) return 0; + if (cg_type_is_aggregate(c, ty) || cg_type_is_void(c, ty)) + return 0; return abi_cg_sizeof(c->abi, access.type) <= (u32)c->target.ptr_size; } @@ -4076,6 +4235,7 @@ void cfree_cg_atomic_load(CfreeCg *g, CfreeCgMemAccess access, val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_load"); + api_require_pointer_value(g, "atomic_load pointer", pty); addr = api_force_reg(g, &ptr, pty); rr = api_alloc_reg_or_spill(g, api_type_class(val_ty), val_ty); dst = api_op_reg(rr, val_ty); @@ -4099,6 +4259,8 @@ void cfree_cg_atomic_store(CfreeCg *g, CfreeCgMemAccess access, val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_store"); + api_require_pointer_value(g, "atomic_store pointer", pty); + api_validate_memory_value(g, "atomic_store", val_ty, api_sv_type(&val)); addr = api_force_reg(g, &ptr, pty); src = api_sv_op_is_reg_or_imm(&val) ? val.op @@ -4124,6 +4286,8 @@ void cfree_cg_atomic_rmw(CfreeCg *g, CfreeCgMemAccess access, val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_rmw"); + api_require_pointer_value(g, "atomic_rmw pointer", pty); + api_validate_memory_value(g, "atomic_rmw", val_ty, api_sv_type(&val)); addr = api_force_reg(g, &ptr, pty); vop = api_sv_op_is_reg_or_imm(&val) ? val.op : api_force_reg(g, &val, val_ty); rr = api_alloc_reg_or_spill(g, api_type_class(val_ty), val_ty); @@ -4140,7 +4304,7 @@ void cfree_cg_atomic_cmpxchg(CfreeCg *g, CfreeCgMemAccess access, CfreeCgMemOrder success, CfreeCgMemOrder failure, int weak) { ApiSValue desired, expected, ptr; - CfreeCgTypeId pty, val_ty, int_ty; + CfreeCgTypeId pty, val_ty, bool_ty; Operand addr, exp_op, des_op, prior, ok; Reg pr, kr; if (!g) @@ -4154,7 +4318,12 @@ void cfree_cg_atomic_cmpxchg(CfreeCg *g, CfreeCgMemAccess access, val_ty = resolve_type(g->c, access.type); if (!val_ty) val_ty = api_atomic_pointee(g, pty, "CfreeCg: atomic_cmpxchg"); - int_ty = builtin_id(CFREE_CG_BUILTIN_I32); + api_require_pointer_value(g, "atomic_cmpxchg pointer", pty); + api_validate_memory_value(g, "atomic_cmpxchg expected", val_ty, + api_sv_type(&expected)); + api_validate_memory_value(g, "atomic_cmpxchg desired", val_ty, + api_sv_type(&desired)); + bool_ty = builtin_id(CFREE_CG_BUILTIN_BOOL); addr = api_force_reg(g, &ptr, pty); exp_op = api_sv_op_is_reg_or_imm(&expected) ? expected.op @@ -4163,9 +4332,9 @@ void cfree_cg_atomic_cmpxchg(CfreeCg *g, CfreeCgMemAccess access, ? desired.op : api_force_reg(g, &desired, val_ty); pr = api_alloc_reg_or_spill(g, api_type_class(val_ty), val_ty); - kr = api_alloc_reg_or_spill(g, RC_INT, int_ty); + kr = api_alloc_reg_or_spill(g, RC_INT, bool_ty); prior = api_op_reg(pr, val_ty); - ok = api_op_reg(kr, int_ty); + ok = api_op_reg(kr, bool_ty); g->target->atomic_cas(g->target, prior, ok, addr, exp_op, des_op, api_mem_for_atomic(g, val_ty), api_map_mem_order(success), api_map_mem_order(failure)); @@ -4173,7 +4342,7 @@ void cfree_cg_atomic_cmpxchg(CfreeCg *g, CfreeCgMemAccess access, api_release(g, &expected); api_release(g, &ptr); api_push(g, api_make_sv(prior, val_ty)); - api_push(g, api_make_sv(ok, int_ty)); + api_push(g, api_make_sv(ok, bool_ty)); } void cfree_cg_atomic_fence(CfreeCg *g, CfreeCgMemOrder order) { @@ -5014,6 +5183,8 @@ void cfree_cg_memcpy(CfreeCg *g, uint64_t size, CfreeCgMemAccess dst_access, T = g->target; src = api_pop(g); dst = api_pop(g); + api_require_pointer_value(g, "memcpy destination", api_sv_type(&dst)); + api_require_pointer_value(g, "memcpy source", api_sv_type(&src)); dst_op = api_force_reg(g, &dst, api_sv_type(&dst)); src_op = api_force_reg(g, &src, api_sv_type(&src)); memset(&agg, 0, sizeof agg); @@ -5039,6 +5210,8 @@ void cfree_cg_memmove(CfreeCg *g, uint64_t size, CfreeCgMemAccess dst_access, } src = api_pop(g); dst = api_pop(g); + api_require_pointer_value(g, "memmove destination", api_sv_type(&dst)); + api_require_pointer_value(g, "memmove source", api_sv_type(&src)); args[0] = api_force_reg(g, &dst, api_sv_type(&dst)); args[1] = api_force_reg(g, &src, api_sv_type(&src)); args[2] = api_op_imm((i64)size, builtin_id(CFREE_CG_BUILTIN_I64)); @@ -5062,6 +5235,7 @@ void cfree_cg_memset(CfreeCg *g, uint8_t val, uint64_t size, } T = g->target; dst = api_pop(g); + api_require_pointer_value(g, "memset destination", api_sv_type(&dst)); dst_op = api_force_reg(g, &dst, api_sv_type(&dst)); byte_val = api_op_imm((i64)val, CFREE_CG_TYPE_NONE); memset(&agg, 0, sizeof agg); diff --git a/test/api/cg_type_test.c b/test/api/cg_type_test.c @@ -5,6 +5,7 @@ #include <stdlib.h> #include <string.h> +#include "core/core.h" #include "obj/obj.h" static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { @@ -27,11 +28,18 @@ static void h_free(CfreeHeap* h, void* p, size_t n) { } static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; +static char g_last_diag[256]; +static int g_suppress_expected_panic_diag; static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, const char* fmt, va_list ap) { + va_list copy; (void)s; (void)loc; + va_copy(copy, ap); + vsnprintf(g_last_diag, sizeof g_last_diag, fmt, copy); + va_end(copy); + if (g_suppress_expected_panic_diag && k == CFREE_DIAG_FATAL) return; fprintf(stderr, "diag %d: ", (int)k); vfprintf(stderr, fmt, ap); fputc('\n', stderr); @@ -51,6 +59,23 @@ static int g_fail; } \ } while (0) +static int expect_panic_contains(CfreeCompiler* c, void (*fn)(void*), + void* arg, const char* expected) { + PanicSave saved; + int panicked = 0; + g_last_diag[0] = '\0'; + compiler_panic_save((Compiler*)c, &saved); + g_suppress_expected_panic_diag++; + if (setjmp(((Compiler*)c)->panic)) { + panicked = 1; + } else { + fn(arg); + } + g_suppress_expected_panic_diag--; + compiler_panic_restore((Compiler*)c, &saved); + return panicked && strstr(g_last_diag, expected) != NULL; +} + static void exercise_cg_handles(CfreeCompiler* c, CfreeCgTypeId i32_ty, int opt_level) { char name_buf[32]; @@ -1082,6 +1107,101 @@ static void exercise_cg_constfold_phases(CfreeCompiler* c, partial_size, local_size); } +typedef struct BadStoreCtx { + CfreeCompiler* c; + CfreeCgTypeId i32_ty; + CfreeCgTypeId i64_ty; + CfreeCgTypeId rec_ty; +} BadStoreCtx; + +static CfreeCg* cg_begin_bad_store_func(CfreeCompiler* c, const char* name) { + CfreeCompileOptions opts; + CfreeObjBuilder* ob; + CfreeCg* cg; + CfreeCgFuncSig sig; + CfreeCgDecl decl; + CfreeCgSym sym; + + memset(&opts, 0, sizeof opts); + ob = (CfreeObjBuilder*)obj_new((Compiler*)c); + EXPECT(ob != NULL, "bad-store obj builder allocation failed"); + if (!ob) return NULL; + cg = cfree_cg_new(c, ob, &opts); + EXPECT(cg != NULL, "bad-store cg allocation failed"); + if (!cg) return NULL; + + memset(&sig, 0, sizeof sig); + sig.ret = cfree_cg_builtin_types(c).id[CFREE_CG_BUILTIN_VOID]; + sig.call_conv = CFREE_CG_CC_TARGET_C; + memset(&decl, 0, sizeof decl); + decl.kind = CFREE_CG_DECL_FUNC; + decl.linkage_name = cfree_sym_intern(c, name); + decl.display_name = decl.linkage_name; + decl.type = cfree_cg_type_func(c, sig); + decl.sym.bind = CFREE_SB_LOCAL; + decl.sym.visibility = CFREE_CG_VIS_DEFAULT; + sym = cfree_cg_decl(cg, decl); + EXPECT(sym != CFREE_CG_SYM_NONE, "bad-store function decl failed"); + cfree_cg_func_begin(cg, sym); + return cg; +} + +static void run_bad_scalar_access_to_aggregate(void* arg) { + BadStoreCtx* ctx = (BadStoreCtx*)arg; + CfreeCgLocal local; + CfreeCgLocalAttrs attrs; + CfreeCgMemAccess mem; + CfreeCg* cg = + cg_begin_bad_store_func(ctx->c, "cg_bad_scalar_access_to_aggregate"); + if (!cg) return; + + memset(&attrs, 0, sizeof attrs); + local = cfree_cg_local(cg, ctx->rec_ty, attrs); + memset(&mem, 0, sizeof mem); + mem.type = ctx->i32_ty; + mem.align = cfree_cg_type_align(ctx->c, ctx->i32_ty); + cfree_cg_push_local(cg, local); + cfree_cg_push_int(cg, 42, ctx->i32_ty); + cfree_cg_store(cg, mem); +} + +static void run_bad_store_value_size(void* arg) { + BadStoreCtx* ctx = (BadStoreCtx*)arg; + CfreeCgLocal local; + CfreeCgLocalAttrs attrs; + CfreeCgMemAccess mem; + CfreeCg* cg = cg_begin_bad_store_func(ctx->c, "cg_bad_store_value_size"); + if (!cg) return; + + memset(&attrs, 0, sizeof attrs); + local = cfree_cg_local(cg, ctx->i64_ty, attrs); + memset(&mem, 0, sizeof mem); + mem.type = ctx->i64_ty; + mem.align = cfree_cg_type_align(ctx->c, ctx->i64_ty); + cfree_cg_push_local(cg, local); + cfree_cg_push_int(cg, 42, ctx->i32_ty); + cfree_cg_store(cg, mem); +} + +static void exercise_cg_memory_mismatch_diags(CfreeCompiler* c, + CfreeCgTypeId i32_ty, + CfreeCgTypeId i64_ty, + CfreeCgTypeId rec_ty) { + BadStoreCtx ctx; + ctx.c = c; + ctx.i32_ty = i32_ty; + ctx.i64_ty = i64_ty; + ctx.rec_ty = rec_ty; + + EXPECT(expect_panic_contains(c, run_bad_scalar_access_to_aggregate, &ctx, + "store scalar/aggregate size mismatch"), + "scalar-to-aggregate store should diagnose clearly, got '%s'", + g_last_diag); + EXPECT(expect_panic_contains(c, run_bad_store_value_size, &ctx, + "store value type/size mismatch"), + "store size mismatch should diagnose clearly, got '%s'", g_last_diag); +} + int main(void) { CfreeTarget target; CfreeEnv env; @@ -1244,6 +1364,7 @@ int main(void) { exercise_cg_data_entsize(c, i8_ty); exercise_cg_literal_folds(c, i32_ty); exercise_cg_constfold_phases(c, i32_ty, i8_ty); + exercise_cg_memory_mismatch_diags(c, i32_ty, i64_ty, rec); cfree_compiler_free(c); return g_fail ? 1 : 0;