commit 3d092914a70e99f5e3707e25f641e221b1695354
parent 82d992202c949f29f917edf6bc55ef99a684a8f6
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 27 Apr 2026 01:08:06 -0700
cc/parse: lvalue-conversion runs for every cast (§L.1)
parse-cast-or-unary skipped rval! when the target type was a pointer,
which missed array-to-pointer decay. (struct s *)buf relabeled buf's
global lval as struct s* but left it pointing at the array storage,
so subsequent dereferences read array bytes as if they were a pointer.
Fix: always rval! before cg-cast. Matches C lvalue-conversion
semantics — arrays decay, lvals become rvals, and the cast bit-casts
the resulting rval to the target type.
§L.1 (flexible array member) falls out for free: the parser already
accepted T name[] via the existing [] suffix arm, complete-agg!
already excluded flex extent via (max sz 0), and cg-push-field +
cg-decay-array already composed correctly.
Lock-in fixture: tests/cc-parse/77-flex-array.c — backs struct s {
int n; int data[]; } with an int[] buffer, casts to struct s *, indexes
through p->data[i]; expects 63.
All suites green on aarch64/amd64/riscv64 (897 tests, 0 failures).
Diffstat:
4 files changed, 44 insertions(+), 10 deletions(-)
diff --git a/cc/parse.scm b/cc/parse.scm
@@ -1343,7 +1343,10 @@
(ty (cdr p)))
(expect-punct ps 'rparen)
(parse-unary ps)
- (cond ((not (ctype-is-ptr? ty)) (rval! ps)) (else #t))
+ ;; Cast operand undergoes lvalue conversion first (C semantics):
+ ;; arrays decay to pointers, lvals become rvals. cg-cast then
+ ;; bit-casts the resulting rval to the target type.
+ (rval! ps)
(cg-cast (ps-cg ps) ty)))
((and (eq? (tok-kind t) 'IDENT) (typedef? ps (tok-value t)))
(advance ps)
@@ -1352,7 +1355,10 @@
(ty (cdr p)))
(expect-punct ps 'rparen)
(parse-unary ps)
- (cond ((not (ctype-is-ptr? ty)) (rval! ps)) (else #t))
+ ;; Cast operand undergoes lvalue conversion first (C semantics):
+ ;; arrays decay to pointers, lvals become rvals. cg-cast then
+ ;; bit-casts the resulting rval to the target type.
+ (rval! ps)
(cg-cast (ps-cg ps) ty)))
(else (advance ps) (parse-expr ps)
(expect-punct ps 'rparen)
diff --git a/docs/CC-PUNCHLIST.md b/docs/CC-PUNCHLIST.md
@@ -417,14 +417,20 @@ responsible for arranging compatible types in the two branches.
### L. Aggregates round 2
-- [ ] **Flexible array member as last struct field**
- - parse: `cc-parse/NN-flex-array.c` — `struct s { int n; int data[]; };`
- indexed via a global instance plus malloc-extra padding.
- tcc.c's `Sym` / `TokenSym` rely on this.
- - Needs: parser accepts `T name[]` only as last field; `complete-agg!`
- sets `ctype-size` to the offset of the flex member (excludes its
- extent); `cg-push-field` for the flex member returns an `arr`-
- typed lval that decays to `ptr` on use.
+- [x] **Flexible array member as last struct field**
+ - parse: `cc-parse/77-flex-array.c` — backs `struct s { int n; int data[]; };`
+ with an `int[]` buffer, casts to `struct s *`, indexes through
+ `p->data[i]`. tcc.c's `Sym` / `TokenSym` rely on this.
+ - Done: parser already accepted `T name[]` (no size) via the existing
+ `parse-decl-suf-cont` `[]` arm; `complete-agg!` already excluded
+ flex extent because `(max sz 0)` collapses `-1` to 0; `cg-push-field`
+ + the indirect-frame path + `cg-decay-array` already composed
+ correctly. The actual fix was a latent cast-conversion bug in
+ `parse-cast-or-unary` — it skipped `rval!` when the target type
+ was a pointer, so `(struct s *)buf` relabeled the array's lval
+ rather than decaying it to a ptr-rval. Now `rval!` runs for every
+ cast (matches C lvalue-conversion semantics: arrays decay, lvals
+ become rvals before the bit-cast).
- [x] **`T[]` in parameter position decays to `T *`**
- parse: `cc-parse/43-array-param-decay.c` — `int sum(int a[], int n)
diff --git a/tests/cc-parse/77-flex-array.c b/tests/cc-parse/77-flex-array.c
@@ -0,0 +1,21 @@
+/* §L.1 flexible array member.
+ *
+ * `int data[]` as the last field of a struct: sizeof excludes the
+ * trailing array, but accessing `s->data[i]` yields the right element
+ * by treating data[] as a pointer to memory immediately after the
+ * named fields.
+ *
+ * We back the struct with a plain int[] buffer and reinterpret-cast
+ * to avoid depending on flex-aware initializer syntax.
+ *
+ * Expected: 3 + 10 + 20 + 30 = 63.
+ */
+
+struct s { int n; int data[]; };
+
+int buf[] = { 3, 10, 20, 30 };
+
+int main(void) {
+ struct s *p = (struct s *)buf;
+ return p->n + p->data[0] + p->data[1] + p->data[2];
+}
diff --git a/tests/cc-parse/77-flex-array.expected-exit b/tests/cc-parse/77-flex-array.expected-exit
@@ -0,0 +1 @@
+63