diff --git a/test/lang/str_arrfield_cap_test.ww b/test/lang/str_arrfield_cap_test.ww new file mode 100644 index 00000000..27d35204 --- /dev/null +++ b/test/lang/str_arrfield_cap_test.ww @@ -0,0 +1,81 @@ +// str_arrfield_cap_test — a str-typed FIELD of an INDEXED element `arr[i].f` +// (N_INDEX-rooted N_DOT) must load the full 24B {ptr,len,cap} header, not just +// {ptr,len}, migrated from test/wcc/936_str_arrfield_cap_run.c (C4.6 arrfield, +// the LAST member of the 3-word-value-read cluster). str is 24B since Phase 2 +// (#1); pre-arrfield the `arr[i].f` str arm loaded 2 words (len in BX, ptr in +// AX) and dropped cap. The miscompile was cs==ww, so the 990-997 byte-id gates +// stayed GREEN while the runtime was wrong — behavioral @test is the net. There +// is no adjacent slice-element sibling at this leaf, so the 3-word triple is +// authored directly to the canonical slice-header ABI (len->BX, cap->CX, +// ptr->AX LAST). +// +// POISONING: the store side of `arr[i].f = v` is a separate, still-broken +// store-side gap (filed; out of scope for this read-side fold). So the value +// elements (A, B) are poisoned through `&arr[i]` + a *struct field write, which +// lands a real cap into the element's +16 word; D points its element at a +// separately-built struct (the proven s1local 3-word field store). +// +// DISCRIMINATION: a 2-word read leaves CX holding whatever the index scale- +// multiply left there, never the poison; spoil() additionally interposes a +// CX-clobbering call between the build and the `arr[i].f` read, so a broken +// 2-word read observes spoil's leftover (44), never the poison cap. + +package str_arrfield_cap_test; + +type rec = struct { f: str }; + +fn spoil() i32 = { + let z: str = "zzzz"; + z.cap = 44i32; + let w: str = z; + return w.cap: i32; +}; + +@test fn arrfield_array_value() void = { + // A — `arr[i].f` value element of a [N]rec local array (LEAQ base). + // Poison cap=8 (len=2) via &arr[1] + a *struct field store. + let p: str = "hi"; + p.cap = 8i32; + let arr: [3]rec; + let pr: *rec = &arr[1]; + pr.f = p; + let junk: i32 = spoil(); + let s: str = arr[1].f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(junk == 44); +}; + +@test fn arrfield_slice_value() void = { + // B — `sl[i].f` value element of a []rec local slice (MOVQ slice.ptr + // base) viewing the same poisoned backing array. Poison cap=9 (len=5). + let p: str = "world"; + p.cap = 9i32; + let arr: [3]rec; + let pr: *rec = &arr[1]; + pr.f = p; + let sl: []rec = arr[0:3]; + let junk: i32 = spoil(); + let s: str = sl[1].f; + assert(s.cap: i32 == 9); + assert(s.len: i32 == 5); + assert(junk == 44); +}; + +@test fn arrfield_ptr_elem() void = { + // D — `arr[i].f` pointer element of a [N]*rec array (LEAQ base, MOVQ + // deref to the *rec, then the leaf field load). The element points at a + // separately-built struct so the poison rides the proven s1local 3-word + // field store, not the broken array-element store. Poison cap=7 (len=3). + let p: str = "abc"; + p.cap = 7i32; + let st: rec; + st.f = p; + let arr: [3]*rec; + arr[1] = &st; + let junk: i32 = spoil(); + let s: str = arr[1].f; + assert(s.cap: i32 == 7); + assert(s.len: i32 == 3); + assert(junk == 44); +}; diff --git a/test/lang/str_arrfield_store_cap_test.ww b/test/lang/str_arrfield_store_cap_test.ww new file mode 100644 index 00000000..fe0c98f8 --- /dev/null +++ b/test/lang/str_arrfield_store_cap_test.ww @@ -0,0 +1,70 @@ +// str_arrfield_store_cap_test — STORING a str into a FIELD of an INDEXED +// element `arr[i].f = v` must write the full 24B {ptr,len,cap} header, not just +// {ptr,len}, migrated from test/wcc/937_str_arrfield_store_cap_run.c (G1 fold). +// str is 24B since Phase 2 (#1); the write-side mirror of the arrfield READ +// (936) previously stored only 2 words (ptr@+0, len@+8) and silently DROPPED +// cap. cs==ww held (byte-id-blind), so behavioral @test is the net. +// +// RHS cap!=len: each test str is a literal whose .cap is mutated to a value +// DISTINCT from its len (a bare literal carries cap==len, hiding a dropped cap). +// PRE-POISON: before the G1 store under test, all three slot words are seeded +// with a DIFFERENT str (ptr='q', len=4, cap=5) via a PROVEN already-3-word store +// path DISTINCT from G1 — A/B via &arr[i] + a *struct field store, D via the +// proven s1local field store (st.f=q). A broken 2-word store never touches +16, +// so the read-back observes the poison cap 5, never 8. The full-triple assert +// (s[0]='h'=104, len=2, cap=8) catches a register slip that clobbers ptr/len. + +package str_arrfield_store_cap_test; + +type rec = struct { f: str }; + +@test fn arrfield_store_array_value() void = { + // A — `arr[i].f = v` into a [N]S local array (LEAQ base, value + // element). Poison the slot (cap=5,len=4,'q') via &arr[1] + a *struct + // field store; then the G1 store lands the test str (cap=8,len=2,'h'). + let q: str = "qqqq"; q.cap = 5i32; + let p: str = "hi"; p.cap = 8i32; + let arr: [3]rec; + let pr: *rec = &arr[1]; + pr.f = q; + arr[1].f = p; + let s: str = arr[1].f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); +}; + +@test fn arrfield_store_slice_value() void = { + // B — `sl[i].f = v` into a []S local slice (MOVQ slice.ptr base, value + // element). The slice views the same backing array; poison the element + // through the array pointer, store and read back through the slice. + let q: str = "qqqq"; q.cap = 5i32; + let p: str = "hi"; p.cap = 8i32; + let arr: [3]rec; + let pr: *rec = &arr[1]; + pr.f = q; + let sl: []rec = arr[0:3]; + sl[1].f = p; + let s: str = sl[1].f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); +}; + +@test fn arrfield_store_ptr_elem() void = { + // D — `arr[i].f = v` into a [N]*S pointer element (LEAQ base, MOVQ deref + // to the *S, then store at the field). The element points at a + // separately-built struct poisoned via the proven s1local field store + // (st.f=q); the G1 store derefs arr[1] and overwrites st.f. + let q: str = "qqqq"; q.cap = 5i32; + let p: str = "hi"; p.cap = 8i32; + let st: rec; + st.f = q; + let arr: [3]*rec; + arr[1] = &st; + arr[1].f = p; + let s: str = arr[1].f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); +}; diff --git a/test/lang/str_chained_field_cap_test.ww b/test/lang/str_chained_field_cap_test.ww new file mode 100644 index 00000000..b178fd63 --- /dev/null +++ b/test/lang/str_chained_field_cap_test.ww @@ -0,0 +1,43 @@ +// str_chained_field_cap_test — a CHAINED str-field read `o.p.f` (depth >= 2, +// where p is a *struct field of o and f is a str field of *p) must load the +// full 24B {ptr,len,cap} header, not just {ptr,len}, migrated from +// test/wcc/934_str_chained_field_cap_run.c (C4.6 case B). str is 24B since +// Phase 2 (#1); pre-caseB the chained *struct N_DOT str arm loaded 2 words (ptr +// in AX, len in BX) and dropped cap. The miscompile was cs==ww, so the 990-997 +// byte-id gates stayed GREEN while the runtime was wrong — behavioral @test is +// the net. This is the chained sibling of str_field_cap_test (direct/single- +// deref S1/S2): the lhs cgexpr leaves AX = the inner *struct pointer, then the +// field is read off AX, cap from +16(AX), base (AX = ptr) read LAST. +// +// DISCRIMINATION: a 2-word read leaves CX untouched, so the row could +// coincidentally pass if CX happened to still carry the poison cap. spoil() +// interposes a CX-clobbering call between the field store and the chained read +// (its own str copy leaves cap=44 in CX), so a broken read observes 44 not 8. + +package str_chained_field_cap_test; + +type inr = struct { f: str }; +type otr = struct { p: *inr }; + +fn spoil() i32 = { + let z: str = "zzzz"; + z.cap = 44i32; + let w: str = z; + return w.cap: i32; +}; + +@test fn caseb_chained_field() void = { + // `o.p.f`: o a struct holding p: *inr, inr holding str field f. Poison + // cap=8 (len=2). + let p: str = "hi"; + p.cap = 8i32; + let ist: inr; + ist.f = p; + let o: otr; + o.p = &ist; + let junk: i32 = spoil(); + let s: str = o.p.f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(junk == 44); +}; diff --git a/test/lang/str_chainfield_store_cap_test.ww b/test/lang/str_chainfield_store_cap_test.ww new file mode 100644 index 00000000..9919cd27 --- /dev/null +++ b/test/lang/str_chainfield_store_cap_test.ww @@ -0,0 +1,59 @@ +// str_chainfield_store_cap_test — STORING a str into a FIELD reached through a +// *struct-VALUED EXPRESSION (`r.sym.f = v`, the chained-N_DOT store) must write +// the full 24B {ptr,len,cap} header, not just {ptr,len}, migrated from +// test/wcc/938_str_chainfield_store_cap_run.c (G2 fold). str is 24B since +// Phase 2 (#1); the chained-store arm previously stored only 2 words (ptr@+0, +// len@+8) and silently DROPPED cap. cs==ww held (byte-id-blind), so behavioral +// @test is the net. +// +// RHS cap!=len: each test str is a literal whose .cap is mutated DISTINCT from +// its len (a bare literal carries cap==len, hiding a dropped cap). PRE-POISON: +// the three slot words are seeded with a DIFFERENT str (ptr='q', len=4, cap=5) +// via the PROVEN already-3-word DIRECT field store (st.f=q, N_IDENT base) — +// never the G2 chained store itself. A broken 2-word store never touches +16, +// so the read-back observes the poison cap 5, never 8. The full-triple assert +// (s[0]='h'=104, len=2, cap=8) catches a register slip that clobbers ptr/len. + +package str_chainfield_store_cap_test; + +type inner = struct { f: str }; +type outer2 = struct { sym: *inner }; +type mid = struct { b: *inner }; +type outer3 = struct { a: *mid }; + +@test fn chainfield_store_depth2() void = { + // A — depth-2 `r.sym.f = v`. r.sym is a *inner pointing at a local st. + // Poison st.f (cap=5,len=4,'q') via the DIRECT s.f=v 3-word store; then + // the G2 chained store lands the test str (cap=8,len=2,'h') through r.sym. + let q: str = "qqqq"; q.cap = 5i32; + let p: str = "hi"; p.cap = 8i32; + let st: inner; + st.f = q; + let r: outer2; + r.sym = &st; + r.sym.f = p; + let s: str = r.sym.f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); +}; + +@test fn chainfield_store_depth3() void = { + // B — depth-3 `r.a.b.f = v`. Two pointer hops (r.a: *mid, mid.b: *inner). + // Poison the leaf via the DIRECT store (leaf.f=q); the G2 chained store + // derefs r.a then .b and overwrites leaf.f. The deeper base eval clobbers + // more registers, confirming the spilled triple survives. + let q: str = "qqqq"; q.cap = 5i32; + let p: str = "hi"; p.cap = 8i32; + let leaf: inner; + leaf.f = q; + let m: mid; + m.b = &leaf; + let r: outer3; + r.a = &m; + r.a.b.f = p; + let s: str = r.a.b.f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); +}; diff --git a/test/lang/str_field_cap_test.ww b/test/lang/str_field_cap_test.ww new file mode 100644 index 00000000..4b70d8f6 --- /dev/null +++ b/test/lang/str_field_cap_test.ww @@ -0,0 +1,68 @@ +// str_field_cap_test — reading a str-typed FIELD of a struct (N_DOT value +// read) must load the full 24B {ptr,len,cap} header, not just {ptr,len}, +// migrated from test/wcc/933_str_field_cap_run.c (C4.6 S1+S2 case A). str is +// 24B since Phase 2 (#1); pre-C4.6 the N_DOT str-field arms loaded 2 words and +// dropped cap. The miscompile was cs==ww, so the 990-997 byte-id gates stayed +// GREEN while the runtime was wrong — a behavioral @test is the net. +// +// The global row is load-bearing: in ww the global struct-field load is a +// separate codegen arm with no slice sibling (cstage folds local+global in one +// base_reg arm; ww splits them), so a cap-drop there would hide from the +// local-field rows — hence its own @test fn. +// +// Each shape POISONS the source str so cap != len (a `.cap =` pseudo-field +// write, no malloc / no import), then observes cap through `let s: str = +// ` (a 3-word copy into the slot) and `s.cap` (an N_IDENT pseudo-field +// read off the slot). spoil() interposes a CX-clobbering call between the field +// store and the read so a broken 2-word read observes spoil's leftover (44), +// not a stale poison the store coincidentally left behind. .len is the control. + +package str_field_cap_test; + +type rec = struct { f: str }; + +let g: rec; + +fn spoil() i32 = { + let z: str = "zzzz"; + z.cap = 44i32; + let w: str = z; + return w.cap: i32; +}; + +@test fn s1local_field() void = { + // `st.f` direct struct local field (base BP). Poison cap=8 (len=2). + let p: str = "hi"; + p.cap = 8i32; + let st: rec; + st.f = p; + let junk: i32 = spoil(); + let s: str = st.f; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(junk == 44); +}; + +@test fn s1global_field() void = { + // `g.f` direct struct global field (base CX path), the no-local-sibling + // arm in ww. Poison cap=9 (len=5). + let p: str = "world"; + p.cap = 9i32; + g.f = p; + let s: str = g.f; + assert(s.cap: i32 == 9); + assert(s.len: i32 == 5); +}; + +@test fn s2ptr_field() void = { + // `pst.f` field via a *struct local (deref, base BX). Poison cap=7 + // (len=3). + let p: str = "abc"; + p.cap = 7i32; + let st: rec; + st.f = p; + let pst: *rec = &st; + let s: str = pst.f; + assert(s.cap: i32 == 7); + assert(s.len: i32 == 3); +}; diff --git a/test/lang/str_massign_store_cap_test.ww b/test/lang/str_massign_store_cap_test.ww new file mode 100644 index 00000000..43b72c0f --- /dev/null +++ b/test/lang/str_massign_store_cap_test.ww @@ -0,0 +1,62 @@ +// str_massign_store_cap_test — a tuple-destructure REASSIGNMENT `a, s = call()` +// (N_MASSIGN) whose str element must write the full 24B {ptr,len,cap} header +// into the str slot, not just {ptr}, migrated from +// test/wcc/939_str_massign_store_cap_run.c (G3 fold). str is 24B since Phase 2 +// (#1); the N_MASSIGN arm previously stored the str element with the bare +// scalar path (one word), dropping len/cap. cs==ww held (byte-id-blind), so +// behavioral @test is the net. +// +// N_MASSIGN vs N_MLET: `let a, s = call()` is N_MLET (fresh bindings) and +// ALREADY destructures 3-word. `a, s = call()` with a, s PRE-DECLARED is +// N_MASSIGN (reassignment) — the arm under test. The bindings MUST be +// pre-declared then reassigned WITHOUT `let`, or the parser emits N_MLET and +// this arm is never reached (silent false green). +// +// RHS cap!=len: the returned str is a literal whose .cap is mutated DISTINCT +// from its len. PRE-POISON: s is seeded with a DIFFERENT str (ptr='q', len=4, +// cap=5) via `let s: str = q` — a PROVEN already-3-word let-init copy, which +// both DECLARES s (so the comma-assign is N_MASSIGN) and poisons its slot. A +// broken 1-word store writes only s.ptr, so the read-back observes the poison +// len 4 / cap 5, never 2 / 8. The full-triple assert (s[0]='h'=104, len=2, +// cap=8) plus the scalar guard (a==5) confirm the XOR branch wires both slots. + +package str_massign_store_cap_test; + +fn mk() (i64, str) = { + let p: str = "hi"; p.cap = 8i32; + return (5i64, p); +}; + +fn mk2() (str, i64) = { + let p: str = "hi"; p.cap = 8i32; + return (p, 5i64); +}; + +@test fn massign_store_str_pos1() void = { + // A — `a, s = mk()`, str the 2nd tuple element (l1). a and s are + // pre-declared (so the comma-assign is N_MASSIGN, not N_MLET); s is + // poisoned (cap=5,len=4,'q') by its let-init copy. The G3 store then + // lands the test str (cap=8,len=2,'h') via (DX,CX,R8)->s, AX->a. + let q: str = "qqqq"; q.cap = 5i32; + let a: i64 = 7i64; + let s: str = q; + a, s = mk(); + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); + assert(a == 5); +}; + +@test fn massign_store_str_pos0() void = { + // B — `s, a = mk2()`, str the 1st tuple element (l0). Same poison and + // test values; exercises the s0_is_str routing (DX,CX,R8)->s, AX->a from + // the other XOR side. + let q: str = "qqqq"; q.cap = 5i32; + let s: str = q; + let a: i64 = 7i64; + s, a = mk2(); + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(s[0] == 104u8); + assert(a == 5); +}; diff --git a/test/lang/str_tuple_elem_cap_test.ww b/test/lang/str_tuple_elem_cap_test.ww new file mode 100644 index 00000000..f8882dd5 --- /dev/null +++ b/test/lang/str_tuple_elem_cap_test.ww @@ -0,0 +1,41 @@ +// str_tuple_elem_cap_test — a tuple POSITIONAL str element read `t.N` (N_IDENT +// base, TY_TUPLE) must load the full 24B {ptr,len,cap} header, not just +// {ptr,len}, migrated from test/wcc/935_str_tuple_elem_cap_run.c (C4.6 S3). str +// is 24B since Phase 2 (#1); pre-S3 the tuple str-element arm loaded 2 words +// (ptr in AX, len in BX) and dropped cap. The miscompile was cs==ww, so the +// 990-997 byte-id gates stayed GREEN while the runtime was wrong — behavioral +// @test is the net. UNLIKE the field/elem arms there is no adjacent slice- +// element arm to fold onto, so the 3-word triple is authored directly to the +// canonical slice-header ABI (AX=ptr, BX=len, CX=cap). +// +// The tuple is built by a (i64, str)-returning mk() so the poisoned cap=8 rides +// the 4-word return ABI into the tuple slot's +24 word (do NOT simplify to a +// local tuple literal — different codegen path); `t.1` then reads it back +// 3-word. spoil() interposes a CX-clobbering call between the build and the +// `t.1` read so a broken 2-word read observes 44 not 8, not a stale CX. + +package str_tuple_elem_cap_test; + +fn mk() (i64, str) = { + let p: str = "hi"; + p.cap = 8i32; + return (5i64, p); +}; + +fn spoil() i32 = { + let z: str = "zzzz"; + z.cap = 44i32; + let w: str = z; + return w.cap: i32; +}; + +@test fn tuple_positional_str_elem() void = { + // `t.1` positional str element of a local (i64, str) tuple. Poison cap=8 + // (len=2) rides mk()'s return ABI into the str element's slot. + let t: (i64, str) = mk(); + let junk: i32 = spoil(); + let s: str = t.1; + assert(s.cap: i32 == 8); + assert(s.len: i32 == 2); + assert(junk == 44); +};