w6c: fold dot chains through inferred-alloc struct pointers (#24 residue)

wwstage dotchainresolve gated its *T roots on an N_TPTR-over-N_TNAME
tnode; an inferred `let p = alloc(S{...})!` rides the checker-
SYNTHESIZED N_TPTR whose pointee is the struct BODY node (the #24
TNAME-normalize covers only the direct struct-lit binding), so the
root gate failed and chained p.field.pseudo reads fell to the
unfused deref arms — shape-only divergence vs cstage's type-keyed
fold (both stages runtime-correct; the historical field(SB) leak the
pin described was already fixed). Widen the local and global root
gates to accept the N_TSTRUCT pointee; correctness stays enforced by
the stamped-tinfo peel below (TY_PTR -> pointee TY_STRUCT).

Graduates alias_g73_heapfill out of DATABYTEID_DIVERGED (2 pins
remain, both the tagged-spill family); adds compile fixtures pinning
the newly-converged siblings (nested read+store, slice .cap,
inferred-global root; pin 1730/22/3460) and a runtime lang row that
mutates then re-points the heap base — a read bypassing the pointer
returns the wrong len.
This commit is contained in:
2026-08-08 17:14:59 +09:00
parent 7631f4327c
commit d78137a051
8 changed files with 79 additions and 14 deletions

View File

@@ -0,0 +1,22 @@
// heapptr_dotread_test — chained field reads through an INFERRED alloc
// pointer must go THROUGH the pointer: mutate the heap str after the
// fill, then re-point the base at a second object — a read that
// bypasses the pointer (stale cached addr or literal-global load)
// returns the wrong len.
package heapptr_dotread_test;
type s1t = str;
type s2t = s1t;
type box = struct { s: s2t, n: int };
@test fn mutate_then_repoint() void = {
let p = alloc(box { s = "hello", n = 5 })!;
assert(p.s.len == 5);
p.s = "worldwide";
assert(p.s.len == 9);
let q = alloc(box { s = "xy", n = 7 })!;
p = q;
assert(p.s.len == 2);
assert(p.n == 7);
};

View File

@@ -1,5 +1,5 @@
//ww:compile
// migrated from test/wcc/944_alias_cgen_b5_run.c: heap struct-lit str-arm fill through a 2-level str alias — pins the absence of the pre-c2 cstage #73 fatal; ww .s carries the #24 field(SB) deref-read leak (DATABYTEID_DIVERGED).
// migrated from test/wcc/944_alias_cgen_b5_run.c: heap struct-lit str-arm fill through a 2-level str alias — pins the absence of the pre-c2 cstage #73 fatal AND the chained deref-read fold through an inferred alloc pointer (the historical #24 field(SB) leak's residue; byte-id since the dotchainresolve N_TSTRUCT-pointee gate).
package main;
type s1t = str;
type s2t = s1t;

View File

@@ -0,0 +1,10 @@
//ww:compile
// INFERRED-global *struct root: the letvartnode gate must accept the
// checker-synthesized N_TPTR-over-N_TSTRUCT exactly as the local arm.
package main;
type box = struct { s: str, n: int };
let gp = alloc(box { s = "hi", n = 1 })!;
export fn main() i32 = {
if (gp.s.len != 2) { return 1; };
return 0;
};

View File

@@ -0,0 +1,16 @@
//ww:compile
// nested-struct chained read + store through an INFERRED alloc pointer:
// the checker-synthesized N_TPTR carries the struct BODY node, and the
// dotchainresolve root gate must still fold (byte-id with the annotated
// twin and with cstage's type-keyed fold).
package main;
type inner = struct { a: int, b: int };
type outer = struct { i: inner, n: int };
export fn main() i32 = {
let p = alloc(outer { i = inner { a = 1, b = 2 }, n = 3 })!;
if (p.i.b != 2) { return 1; };
p.i.b = 9;
if (p.i.b != 9) { return 2; };
if (p.n != 3) { return 3; };
return 0;
};

View File

@@ -0,0 +1,12 @@
//ww:compile
// slice-typed field pseudo-read (.cap) through an INFERRED alloc
// pointer — the slice sibling of the str-arm fold.
package main;
type box = struct { sl: []int, n: int };
export fn main() i32 = {
let s: []int = alloc([], 4)!;
let p = alloc(box { sl = s, n = 5 })!;
if (p.sl.cap != 4) { return 1; };
if (p.n != 5) { return 2; };
return 0;
};