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:
7
Makefile
7
Makefile
@@ -730,12 +730,7 @@ DATABYTEID_EXPECTED_MIN = 915
|
||||
# routes the cross-module tagged call-result through a 16B scratch pair
|
||||
# where cstage PUSHQes AX/DX (frame 16 vs 32); runtime-equivalent
|
||||
# (both stages run exit 42).
|
||||
# alias_g73_heapfill: held divergence — the ww-side heap struct-lit
|
||||
# str-arm deref-field READ leaks a field(SB) load (task #24); both
|
||||
# stages frontend-compile rc 0, only the .s differs. Graduates out
|
||||
# when #24 lands.
|
||||
DATABYTEID_DIVERGED = r700_strings_byteindex r839_xmod_nominal_match \
|
||||
alias_g73_heapfill
|
||||
DATABYTEID_DIVERGED = r700_strings_byteindex r839_xmod_nominal_match
|
||||
$(if $(DATABYTEID_FILES),,$(error test-byteid: empty data corpus))
|
||||
test-data-byteid: $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww
|
||||
@work=$$(mktemp -d "$(CURDIR)/$(DATABYTEID_DIR).XXXXXX") || exit 1; \
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package wwfixture;
|
||||
|
||||
def protocolversion: i32 = 1;
|
||||
def corpuscount: i32 = 1727;
|
||||
def corpuscount: i32 = 1730;
|
||||
def errorcount: i32 = 343;
|
||||
def compilecount: i32 = 19;
|
||||
def compilecount: i32 = 22;
|
||||
def runcount: i32 = 209;
|
||||
def runexitcount: i32 = 1156;
|
||||
def nativecount: i32 = 3454;
|
||||
def corpushash: str = "40ad5fd79515a743f2adb53e4a26396c6f13859df956fa079299a4b459594177";
|
||||
def nativecount: i32 = 3460;
|
||||
def corpushash: str = "5c20c19b826359fcceb45c8db5cf405f39183a68783582eaaf5f38f0e88f3771";
|
||||
|
||||
type directive = enum i32 {
|
||||
ERROR = 0,
|
||||
|
||||
@@ -5403,11 +5403,20 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node,
|
||||
// `*T` root (param/local): dereference at emit time;
|
||||
// pointee struct supplies the field layout. Callers
|
||||
// that opt in via *outptrroot emit a MOVQ load of the
|
||||
// slot before indexing.
|
||||
// slot before indexing. N_TSTRUCT pointee: 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); correctness is enforced by the stamped-tinfo
|
||||
// peel below (TY_PTR → pointee TY_STRUCT), mirroring
|
||||
// cstage's type-keyed root gate (cgen.c cg N_DOT
|
||||
// chained-spine ptr_root) — this kind check is only a
|
||||
// shape filter.
|
||||
if (lc.tnode.kind == syntax.nkind.N_TPTR) {
|
||||
let pe: *syntax.node = lc.tnode.lhs;
|
||||
if (pe != nil) {
|
||||
if (pe.kind == syntax.nkind.N_TNAME) {
|
||||
if (pe.kind == syntax.nkind.N_TNAME
|
||||
|| pe.kind == syntax.nkind.N_TSTRUCT) {
|
||||
*outrootoff = lc.off;
|
||||
*outptrroot = true;
|
||||
resolved = true;
|
||||
@@ -5429,7 +5438,8 @@ export fn dotchainresolve(c: *cgen, n: *syntax.node,
|
||||
if (gtn.kind == syntax.nkind.N_TPTR) {
|
||||
let pe: *syntax.node = gtn.lhs;
|
||||
if (pe != nil) {
|
||||
if (pe.kind == syntax.nkind.N_TNAME) {
|
||||
if (pe.kind == syntax.nkind.N_TNAME
|
||||
|| pe.kind == syntax.nkind.N_TSTRUCT) {
|
||||
*outisglobal = true;
|
||||
*outptrroot = true;
|
||||
resolved = true;
|
||||
|
||||
22
test/lang/heapptr_dotread_test.ww
Normal file
22
test/lang/heapptr_dotread_test.ww
Normal 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);
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
10
test/wcc/data/alias_infptr_global/case.ww
Normal file
10
test/wcc/data/alias_infptr_global/case.ww
Normal 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;
|
||||
};
|
||||
16
test/wcc/data/alias_infptr_nest/case.ww
Normal file
16
test/wcc/data/alias_infptr_nest/case.ww
Normal 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;
|
||||
};
|
||||
12
test/wcc/data/alias_infptr_slicecap/case.ww
Normal file
12
test/wcc/data/alias_infptr_slicecap/case.ww
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user