commit 15fda5605f98b2ab9473d170d76e01322528cbe0
parent 71b2a2aa5e6017120efb570f33e9c2a15e46e391
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 19 May 2026 14:30:41 -0700
Fix brace-elided aggregate initializers
Diffstat:
2 files changed, 202 insertions(+), 51 deletions(-)
diff --git a/doc/BUGS.md b/doc/BUGS.md
@@ -0,0 +1,5 @@
+Known bugs with red test cases (test-parse)
+
+- [ ] K&R old-style function definitions with declaration lists: 6_9_14_kr_function_def_params
+- [ ] K&R promoted parameter declarations, e.g. char: 6_9_15_kr_function_def_promoted_char
+- [ ] Non-extern block-scope function declarations resolving to external definitions: 6_9_16_block_scope_func_decl
diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c
@@ -52,8 +52,16 @@ static u64 decode_lit_unit_le(const u8* src, u32 size) {
/* Forward declaration for mutual recursion. */
void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset,
const Type* ty);
-static u32 init_elided(Parser* p, FrameSlot slot, const Type* arr_ty,
- u32 offset, const Type* ty);
+
+typedef struct InitDesignatorCont {
+ const Type* parent_ty;
+ u32 parent_offset;
+ u32 next_index;
+} InitDesignatorCont;
+
+static void init_aggregate_remainder(Parser* p, FrameSlot slot,
+ const Type* arr_ty, u32 offset,
+ const Type* ty, u32 start_index);
/* Push the lvalue of a sub-object at byte offset `offset` within the array
* local `slot` (whose type is `arr_ty`), with element type `elem_ty`. */
@@ -254,15 +262,20 @@ static void init_string_at(Parser* p, FrameSlot slot, const Type* arr_ty,
/* Parse a designator chain (`[const]` and `.ident` repeats) ending at `=`. */
static void parse_designator_chain(Parser* p, const Type* outer_ty,
u32 outer_offset, const Type** sub_ty_out,
- u32* sub_offset_out, u32* top_index_out) {
+ u32* sub_offset_out, u32* top_index_out,
+ InitDesignatorCont* cont_out) {
const Type* cur_ty = outer_ty;
u32 cur_off = outer_offset;
int first = 1;
+ InitDesignatorCont cont;
+ memset(&cont, 0, sizeof cont);
for (;;) {
if (is_punct(&p->cur, '[')) {
i64 idx;
u32 esz;
SrcLoc cloc = tok_loc_init(&p->cur);
+ const Type* parent_ty = cur_ty;
+ u32 parent_off = cur_off;
advance(p);
idx = eval_const_int(p, cloc);
expect_punct(p, ']', "']' after designator index");
@@ -275,6 +288,9 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty,
esz = c_abi_sizeof(p->abi, cur_ty->arr.elem);
cur_off += (u32)idx * esz;
cur_ty = cur_ty->arr.elem;
+ cont.parent_ty = parent_ty;
+ cont.parent_offset = parent_off;
+ cont.next_index = (u32)idx + 1u;
if (first) *top_index_out = (u32)idx;
first = 0;
} else if (is_punct(&p->cur, '.')) {
@@ -283,6 +299,10 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty,
u32 foff;
const Field* ff;
u16 fi;
+ u32 selected_index = 0;
+ int have_selected_index = 0;
+ const Type* parent_ty = cur_ty;
+ u32 parent_off = cur_off;
advance(p);
if (p->cur.kind != TOK_IDENT ||
ident_kw_init(p, p->cur.v.ident) != KW_NONE) {
@@ -297,26 +317,37 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty,
perr(p, "no such field in designator");
}
cur_off += foff;
- if (first) {
- for (fi = 0; fi < cur_ty->rec.nfields; ++fi) {
- const Field* g = &cur_ty->rec.fields[fi];
- if (g->name == fname && fname != 0) {
+ for (fi = 0; fi < cur_ty->rec.nfields; ++fi) {
+ const Field* g = &cur_ty->rec.fields[fi];
+ if (g->name == fname && fname != 0) {
+ selected_index = fi;
+ have_selected_index = 1;
+ if (first) {
*top_index_out = fi;
- break;
}
- if ((g->flags & FIELD_ANON) &&
- (g->type->kind == TY_STRUCT || g->type->kind == TY_UNION)) {
- const Type* tmp_ty;
- u32 tmp_off;
- const Field* tmp_f;
- if (find_field(p->abi, p->pool, g->type, fname, &tmp_ty, &tmp_off,
- &tmp_f)) {
+ break;
+ }
+ if ((g->flags & FIELD_ANON) &&
+ (g->type->kind == TY_STRUCT || g->type->kind == TY_UNION)) {
+ const Type* tmp_ty;
+ u32 tmp_off;
+ const Field* tmp_f;
+ if (find_field(p->abi, p->pool, g->type, fname, &tmp_ty, &tmp_off,
+ &tmp_f)) {
+ selected_index = fi;
+ have_selected_index = 1;
+ if (first) {
*top_index_out = fi;
- break;
}
+ break;
}
}
}
+ if (have_selected_index) {
+ cont.parent_ty = parent_ty;
+ cont.parent_offset = parent_off;
+ cont.next_index = selected_index + 1u;
+ }
cur_ty = fty;
first = 0;
} else {
@@ -327,6 +358,28 @@ static void parse_designator_chain(Parser* p, const Type* outer_ty,
expect_punct(p, '=', "'=' after designator");
*sub_ty_out = cur_ty;
*sub_offset_out = cur_off;
+ if (cont_out) *cont_out = cont;
+}
+
+static int aggregate_has_index(const Type* ty, u32 index) {
+ if (!ty) return 0;
+ if (ty->kind == TY_ARRAY) return index < ty->arr.count;
+ if (ty->kind == TY_STRUCT) return index < ty->rec.nfields;
+ return 0;
+}
+
+static int designator_continues_inside(Parser* p, const Type* outer_ty,
+ u32 outer_offset,
+ const Type* top_ty, u32 top_offset,
+ const InitDesignatorCont* cont) {
+ u32 top_size;
+ if (!cont || !cont->parent_ty) return 0;
+ if (cont->parent_ty == outer_ty && cont->parent_offset == outer_offset)
+ return 0;
+ if (!aggregate_has_index(cont->parent_ty, cont->next_index)) return 0;
+ top_size = c_abi_sizeof(p->abi, top_ty);
+ return cont->parent_offset >= top_offset &&
+ cont->parent_offset - top_offset <= top_size;
}
static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty,
@@ -341,7 +394,9 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty,
const Type* sub_ty;
u32 sub_off;
u32 top_idx = 0;
- parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx);
+ InitDesignatorCont cont;
+ parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx,
+ &cont);
while (zero_lo < top_idx) {
const Field* zf = &ty->rec.fields[zero_lo];
if (zf->flags & FIELD_BITFIELD) {
@@ -358,6 +413,16 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty,
++zero_lo;
}
init_at(p, slot, arr_ty, sub_off, sub_ty);
+ {
+ const Field* top_f = &ty->rec.fields[top_idx];
+ u32 top_off = offset + L->fields[top_idx].offset;
+ if (designator_continues_inside(p, ty, offset, top_f->type, top_off,
+ &cont) &&
+ accept_punct(p, ',') && !is_punct(&p->cur, '}')) {
+ init_aggregate_remainder(p, slot, arr_ty, cont.parent_offset,
+ cont.parent_ty, cont.next_index);
+ }
+ }
i = top_idx;
if (zero_lo <= top_idx) zero_lo = top_idx + 1;
goto next_item_struct;
@@ -398,36 +463,48 @@ static u32 init_struct_fields(Parser* p, FrameSlot slot, const Type* arr_ty,
return i;
}
-static u32 init_elided(Parser* p, FrameSlot slot, const Type* arr_ty,
- u32 offset, const Type* ty) {
+static void init_aggregate_remainder(Parser* p, FrameSlot slot,
+ const Type* arr_ty, u32 offset,
+ const Type* ty, u32 start_index) {
if (ty->kind == TY_ARRAY) {
u32 esz = c_abi_sizeof(p->abi, ty->arr.elem);
- init_at(p, slot, arr_ty, offset, ty->arr.elem);
- (void)esz;
- return 1;
+ u32 i;
+ for (i = start_index; i < ty->arr.count; ++i) {
+ init_at(p, slot, arr_ty, offset + i * esz, ty->arr.elem);
+ if (i + 1u >= ty->arr.count) return;
+ if (!accept_punct(p, ',')) break;
+ if (is_punct(&p->cur, '}')) break;
+ }
+ for (++i; i < ty->arr.count; ++i) {
+ zero_init_at(p, slot, arr_ty, offset + i * esz, ty->arr.elem);
+ }
+ return;
}
if (ty->kind == TY_STRUCT) {
- init_struct_fields(p, slot, arr_ty, offset, ty, 0, /*braced=*/0);
- return 1;
- }
- /* Scalar / pointer / union: consume one assignment-expr. */
- int had_brace = accept_punct(p, '{');
- push_subobject_lv(p, slot, arr_ty, offset, ty);
- parse_assign_expr(p);
- to_rvalue(p);
- {
- const Type* rhs = cg_top_type(p->cg);
- CSemCheck chk = c_sem_check_assignment(p->pool, ty, rhs, C_SEM_ASSIGN_INIT);
- if (!chk.ok) perr(p, "%s", chk.message);
- }
- coerce_top_to_lvalue(p);
- cg_store(p->cg);
- cg_drop(p->cg);
- if (had_brace) {
- accept_punct(p, ',');
- expect_punct(p, '}', "'}' after scalar initializer");
+ const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, ty);
+ u32 i;
+ for (i = start_index; i < ty->rec.nfields; ++i) {
+ init_field_at(p, slot, arr_ty, offset, ty, i);
+ if (i + 1u >= ty->rec.nfields) return;
+ if (!accept_punct(p, ',')) break;
+ if (is_punct(&p->cur, '}')) break;
+ }
+ for (++i; i < ty->rec.nfields; ++i) {
+ const Field* f = &ty->rec.fields[i];
+ if (f->flags & FIELD_BITFIELD) {
+ if (!(f->flags & FIELD_ZERO_WIDTH)) {
+ push_record_field_lv(p, slot, arr_ty, offset, ty, i);
+ cg_push_int(p->cg, 0, f->type);
+ cg_store(p->cg);
+ cg_drop(p->cg);
+ }
+ } else {
+ zero_init_at(p, slot, arr_ty, offset + L->fields[i].offset, f->type);
+ }
+ }
+ return;
}
- return 1;
+ init_at(p, slot, arr_ty, offset, ty);
}
void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset,
@@ -451,7 +528,7 @@ void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset,
}
}
if (!is_punct(&p->cur, '{')) {
- init_elided(p, slot, arr_ty, offset, elem_ty);
+ init_aggregate_remainder(p, slot, arr_ty, offset, ty, 0);
return;
}
advance(p); /* '{' */
@@ -464,12 +541,20 @@ void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset,
const Type* sub_ty;
u32 sub_off;
u32 top_idx = 0;
- parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx);
+ InitDesignatorCont cont;
+ parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx,
+ &cont);
while (zero_lo < top_idx) {
zero_init_at(p, slot, arr_ty, offset + zero_lo * esz, elem_ty);
++zero_lo;
}
init_at(p, slot, arr_ty, sub_off, sub_ty);
+ if (designator_continues_inside(
+ p, ty, offset, elem_ty, offset + top_idx * esz, &cont) &&
+ accept_punct(p, ',') && !is_punct(&p->cur, '}')) {
+ init_aggregate_remainder(p, slot, arr_ty, cont.parent_offset,
+ cont.parent_ty, cont.next_index);
+ }
i = top_idx + 1;
if (zero_lo < i) zero_lo = i;
} else {
@@ -496,7 +581,7 @@ void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset,
}
if (ty->kind == TY_STRUCT) {
if (!is_punct(&p->cur, '{')) {
- init_struct_fields(p, slot, arr_ty, offset, ty, 0, /*braced=*/0);
+ init_aggregate_remainder(p, slot, arr_ty, offset, ty, 0);
return;
}
advance(p); /* '{' */
@@ -514,7 +599,7 @@ void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset,
const Type* sub_ty;
u32 sub_off;
u32 top_idx = 0;
- parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx);
+ parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx, NULL);
init_at(p, slot, arr_ty, sub_off, sub_ty);
} else {
const Field* f = &ty->rec.fields[0];
@@ -1047,6 +1132,44 @@ static void parse_static_bitfield_at(Parser* p, u8* buf, u32 buflen,
encode_uint_le(buf + storage_off, storage_size, cur);
}
+static void parse_static_aggregate_remainder(Parser* p, u8* buf, u32 buflen,
+ u32 offset, const Type* ty,
+ u32 start_index) {
+ if (ty->kind == TY_ARRAY) {
+ const Type* elem = ty->arr.elem;
+ u32 esz = c_abi_sizeof(p->abi, elem);
+ u32 i;
+ for (i = start_index; i < ty->arr.count; ++i) {
+ parse_static_init_at(p, buf, buflen, offset + i * esz, elem);
+ if (i + 1u >= ty->arr.count) return;
+ if (!accept_punct(p, ',')) break;
+ if (is_punct(&p->cur, '}')) break;
+ }
+ return;
+ }
+ if (ty->kind == TY_STRUCT) {
+ const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, ty);
+ u32 i;
+ for (i = start_index; i < ty->rec.nfields; ++i) {
+ const Field* f = &ty->rec.fields[i];
+ if (f->flags & FIELD_BITFIELD) {
+ if (!(f->flags & FIELD_ZERO_WIDTH)) {
+ parse_static_bitfield_at(p, buf, buflen, offset, &L->fields[i],
+ f->type);
+ }
+ } else {
+ parse_static_init_at(p, buf, buflen, offset + L->fields[i].offset,
+ f->type);
+ }
+ if (i + 1u >= ty->rec.nfields) return;
+ if (!accept_punct(p, ',')) break;
+ if (is_punct(&p->cur, '}')) break;
+ }
+ return;
+ }
+ parse_static_init_at(p, buf, buflen, offset, ty);
+}
+
void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
const Type* ty) {
if (ty->kind == TY_ARRAY) {
@@ -1071,7 +1194,8 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
}
had_brace = accept_punct(p, '{');
if (!had_brace) {
- perr(p, "expected '{' for static-storage array initializer");
+ parse_static_aggregate_remainder(p, buf, buflen, offset, ty, 0);
+ return;
}
if (!is_punct(&p->cur, '}')) {
for (;;) {
@@ -1079,8 +1203,16 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
const Type* sub_ty;
u32 sub_off;
u32 top_idx = 0;
- parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx);
+ InitDesignatorCont cont;
+ parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx,
+ &cont);
parse_static_init_at(p, buf, buflen, sub_off, sub_ty);
+ if (designator_continues_inside(
+ p, ty, offset, elem, offset + top_idx * esz, &cont) &&
+ accept_punct(p, ',') && !is_punct(&p->cur, '}')) {
+ parse_static_aggregate_remainder(p, buf, buflen, cont.parent_offset,
+ cont.parent_ty, cont.next_index);
+ }
i = top_idx + 1;
} else {
if (i >= ty->arr.count) {
@@ -1101,7 +1233,8 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
const ABIRecordLayout* L = c_abi_record_layout(p->abi, p->pool, ty);
u32 i = 0;
if (!had_brace) {
- perr(p, "expected '{' for static-storage struct initializer");
+ parse_static_aggregate_remainder(p, buf, buflen, offset, ty, 0);
+ return;
}
while (i < ty->rec.nfields && !is_punct(&p->cur, '}')) {
const Field* f = &ty->rec.fields[i];
@@ -1109,8 +1242,21 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
const Type* sub_ty;
u32 sub_off;
u32 top_idx = 0;
- parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx);
+ InitDesignatorCont cont;
+ parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx,
+ &cont);
parse_static_init_at(p, buf, buflen, sub_off, sub_ty);
+ {
+ const Field* top_f = &ty->rec.fields[top_idx];
+ u32 top_off = offset + L->fields[top_idx].offset;
+ if (designator_continues_inside(p, ty, offset, top_f->type, top_off,
+ &cont) &&
+ accept_punct(p, ',') && !is_punct(&p->cur, '}')) {
+ parse_static_aggregate_remainder(
+ p, buf, buflen, cont.parent_offset, cont.parent_ty,
+ cont.next_index);
+ }
+ }
i = top_idx + 1;
if (!accept_punct(p, ',')) break;
continue;
@@ -1140,7 +1286,7 @@ void parse_static_init_at(Parser* p, u8* buf, u32 buflen, u32 offset,
const Type* sub_ty;
u32 sub_off;
u32 top_idx = 0;
- parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx);
+ parse_designator_chain(p, ty, offset, &sub_ty, &sub_off, &top_idx, NULL);
(void)top_idx;
parse_static_init_at(p, buf, buflen, sub_off, sub_ty);
} else {