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:
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;