← board

A struct assignment passed by value copies only eight bytes

Found 2026-08-16 reducing csmith seed 91110, the finding [[feature-c-csmith-differential-fuzzing]] left unreduced at the end of the previous session.

Reduced repro

struct S1 { int f0; long long f1; };
static struct S1 g = {1, 2};
static void take(struct S1 p) { printf("f0=%d f1=%lld\n", p.f0, p.f1); }
int main(void) {
  struct S1 l = {0x6F1F9E91, 0xAD9135A7A9E0DFB1LL};
  struct S1 *pg = &g;
  take(((*pg) = l));
}
f0 f1
gcc 1864343185 -5939907439299076175
pxx 1864343185 4431544

The STORE to g was correct in both. Only the value handed to the callee was short.

Root cause

ResolveNodeRec answers "which record is this node's". It had arms for IDENT, FIELD, INDEX, CALL, comma, casts, compound literals — and none for AN_ASSIGN, so a struct assignment used as a value came back REC_NONE. The C by-value argument path (needTemp in ir.inc) then sized its copy with RecSize(REC_NONE), which is the 8-byte fallback:

20: copy_rec a=18 b=19 ival=16      <- the assignment itself: right
22: copy_rec a=17 b=21 ival=8       <- the argument temp: half the struct

So the first eightbyte was copied and the second was whatever the temp's stack slot held.

The fix is the arm, not the size computation: an assignment yields the value STORED, so its record is the DESTINATION's — literally the same sentence the comma arm two lines above already states for its right operand, and the type half of the aggregate rule the AN_ASSIGN lowering in ir.inc states for its value. Result := ResolveNodeRec(ASTLeft[node]).

Why the corpora missed it

Same reason as its sibling [[bug-c-a-struct-assignment-used-as-a-value-runs-its-rhs-twice]] (csmith seed 90202, previous session): a person writes *p = s; f(*p);. The chained form is legal C that hand-written code has no reason to produce, and the failure is a plausible wrong value in the SECOND half of a struct, not a crash — every value derived from the first field stayed right. lua, sqlite, tcc, zlib and the c-testsuite all pass with the bug present.

Gate

make compiler/pascal26 (self-host fixedpoint, byte-identical) + tools/gate.sh quick GREEN. Seed 91110's full 1723-line program now agrees with gcc on every checksum. Pinned by test/cstruct_assign_value_byvalue_arg.c in make test: deref destination, named destination, a 24-byte struct too large for any register pair, and two such arguments in one call.

Log