wcc: opaque use-guards — reject every unsized use (incl tuple/tagged, recursive) (#108)
#108 sub-fold (b): close the footgun #108(a) opened. opaque is abstract and UNSIZED (size = align = SIZE_UNDEFINED = (u64)-1), legal only behind indirection. Without guards a bare use would fabricate a (u64)-1-byte slot — a silent miscompile (rule 7). opaque is illegal by-value in FOUR aggregate positions (array element, struct field, tuple member, tagged- union variant) + as a bare value, under size/align, and as a []opaque element-index. LOUD guards, mirroring harec's scattered `size == SIZE_UNDEFINED` checks: 1. bare value/local/param/return-by-value (check.c clet, build_fn_type, top-level let; harec check.c:1524, :3931) 2. opaque struct field (resolve_type N_TSTRUCT) 3. [N]opaque array element (resolve_type N_TARRAY) 3t. opaque tuple member (resolve_type N_TTUPLE; harec type_store.c:1147) 3u. opaque tagged-union variant (resolve_type N_TTAGGED; harec type_store.c:449) 4. size(opaque) / align(opaque) (size/align fold; harec check.c:2720) 5. indexing []opaque (N_INDEX; harec check.c:384) Detection is via the SIZE_UNDEFINED sentinel the guard consults, so the sized forms `*opaque` (8B) and `[]opaque` (24B header) pass untouched. Rule-10 per-guard stage placement: - Guards 1/2/3/3t/3u/5 are CSTAGE-ONLY. The wwstage check.ww is an AST-level approximation with no binding-size computation (g1) and no type-decl field/element/member validation walk (g2/g3/3t/3u); its N_INDEX indexresult returns the element type without consulting its size and defers invalid-index rejection to the cstage (g5). Same cstage-only neg-case precedent as 712_redecl / 708_param_shadow_mod. - Guard 4 is BOTH-STAGES. The wwstage HAS the size()/align() fold (astsize/astalign would otherwise fold opaque to a bogus 0 — a silent miscompile); twinned via astunsized + deffolderr. Because the wwstage has NO per-construction guards, its fold alone must catch every opaque-containing type: astunsized is RECURSIVE — a type is unsized iff it is opaque OR an aggregate (array/struct/tuple/tagged) with a recursively-unsized member. This both reaches the tuple/tagged folds AND closes the leaf-only size([4]opaque)/size(struct{x:opaque})→0 leak. The cstage size/align guard stays leaf — the cstage rejects unsized aggregates at construction, so its fold only ever sees a leaf. opaque is unused by the bootstrap, so every guard is inert on the selfhost corpus — 990-997 stay byte-identical. Regenerates the w6c/wwdump combined.ww (check.ww embed). New compile-fail probe 961_opaque_guards (14 build-fails rows incl tuple/tagged/nested + 2 *opaque/[]opaque positive controls); 960 positive probe unchanged.
This commit is contained in:
@@ -7999,6 +7999,59 @@ fn astsize(c: *checker, t: *node) i64 = {
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// astunsized — #108(b): true iff `t` contains an unsized component. A
|
||||
// type is unsized iff it is the abstract `opaque` (size/align ==
|
||||
// SIZE_UNDEFINED) OR an aggregate (array / struct / tuple / tagged)
|
||||
// with a recursively-unsized member. The wwstage has NO type-decl
|
||||
// construction guards (those are cstage-only, rule-10), so its size()/
|
||||
// align() FOLD must detect every opaque-containing type itself — a
|
||||
// leaf-only check would silently fold size([4]opaque) / size(struct{x:
|
||||
// opaque}) / size((opaque, i32)) to garbage (rule 7). Does NOT peel
|
||||
// TPTR/TSLICE/TCHAN/TFN — `*opaque` (8B) and `[]opaque` (24B header)
|
||||
// are sized and legal behind indirection. Cstage twin: the leaf
|
||||
// `m == SIZE_UNDEFINED` size/align guard PLUS the per-construction
|
||||
// require_sized guards that reject unsized aggregates at the type decl
|
||||
// (so the cstage size/align fold only ever sees a leaf opaque); harec
|
||||
// ref/harec/src/check.c:2720, type_store.c:1147 (tuple) / :449 (tagged).
|
||||
fn astunsized(c: *checker, t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return false; };
|
||||
let k: nkind = u.kind;
|
||||
if (k == nkind.N_TNAME) {
|
||||
if (streq(u.str, "opaque")) { return true; };
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TARRAY) { return astunsized(c, u.lhs); };
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let p: *node = u.list;
|
||||
for (p != nil) {
|
||||
if (astunsized(c, p.lhs)) { return true; };
|
||||
p = p.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TSTRUCT) {
|
||||
let f: *node = u.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
if (astunsized(c, f.lhs)) { return true; };
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TTAGGED) {
|
||||
let v: *node = u.list;
|
||||
for (v != nil) {
|
||||
if (astunsized(c, v)) { return true; };
|
||||
v = v.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// matchyieldtype — port of cstage cmd/wcc/check.c:110-135. Walks a
|
||||
// match arm body for the first `yield expr;` and returns its operand
|
||||
// type. Returns nil if no yield is reachable from `body`. Doesn't
|
||||
@@ -9183,12 +9236,20 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// own type.
|
||||
let utn: *node = mktname(c, "untyped_int");
|
||||
if (issize) {
|
||||
// #108(b): rule-10 twin of the cstage
|
||||
// size/align unsized guard.
|
||||
if (astunsized(c, e.list)) {
|
||||
deffolderr(c, e, "cannot take size of unsized type 'opaque'");
|
||||
};
|
||||
let v: i64 = astsize(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
e.type_ = tinfofornode(c, utn): *void;
|
||||
return mktname(c, "i32");
|
||||
};
|
||||
if (isalign) {
|
||||
if (astunsized(c, e.list)) {
|
||||
deffolderr(c, e, "cannot take align of unsized type 'opaque'");
|
||||
};
|
||||
let v: i64 = astalign(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
e.type_ = tinfofornode(c, utn): *void;
|
||||
|
||||
@@ -848,6 +848,59 @@ fn astsize(c: *checker, t: *node) i64 = {
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// astunsized — #108(b): true iff `t` contains an unsized component. A
|
||||
// type is unsized iff it is the abstract `opaque` (size/align ==
|
||||
// SIZE_UNDEFINED) OR an aggregate (array / struct / tuple / tagged)
|
||||
// with a recursively-unsized member. The wwstage has NO type-decl
|
||||
// construction guards (those are cstage-only, rule-10), so its size()/
|
||||
// align() FOLD must detect every opaque-containing type itself — a
|
||||
// leaf-only check would silently fold size([4]opaque) / size(struct{x:
|
||||
// opaque}) / size((opaque, i32)) to garbage (rule 7). Does NOT peel
|
||||
// TPTR/TSLICE/TCHAN/TFN — `*opaque` (8B) and `[]opaque` (24B header)
|
||||
// are sized and legal behind indirection. Cstage twin: the leaf
|
||||
// `m == SIZE_UNDEFINED` size/align guard PLUS the per-construction
|
||||
// require_sized guards that reject unsized aggregates at the type decl
|
||||
// (so the cstage size/align fold only ever sees a leaf opaque); harec
|
||||
// ref/harec/src/check.c:2720, type_store.c:1147 (tuple) / :449 (tagged).
|
||||
fn astunsized(c: *checker, t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return false; };
|
||||
let k: nkind = u.kind;
|
||||
if (k == nkind.N_TNAME) {
|
||||
if (streq(u.str, "opaque")) { return true; };
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TARRAY) { return astunsized(c, u.lhs); };
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let p: *node = u.list;
|
||||
for (p != nil) {
|
||||
if (astunsized(c, p.lhs)) { return true; };
|
||||
p = p.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TSTRUCT) {
|
||||
let f: *node = u.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
if (astunsized(c, f.lhs)) { return true; };
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TTAGGED) {
|
||||
let v: *node = u.list;
|
||||
for (v != nil) {
|
||||
if (astunsized(c, v)) { return true; };
|
||||
v = v.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// matchyieldtype — port of cstage cmd/wcc/check.c:110-135. Walks a
|
||||
// match arm body for the first `yield expr;` and returns its operand
|
||||
// type. Returns nil if no yield is reachable from `body`. Doesn't
|
||||
@@ -2032,12 +2085,20 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// own type.
|
||||
let utn: *node = mktname(c, "untyped_int");
|
||||
if (issize) {
|
||||
// #108(b): rule-10 twin of the cstage
|
||||
// size/align unsized guard.
|
||||
if (astunsized(c, e.list)) {
|
||||
deffolderr(c, e, "cannot take size of unsized type 'opaque'");
|
||||
};
|
||||
let v: i64 = astsize(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
e.type_ = tinfofornode(c, utn): *void;
|
||||
return mktname(c, "i32");
|
||||
};
|
||||
if (isalign) {
|
||||
if (astunsized(c, e.list)) {
|
||||
deffolderr(c, e, "cannot take align of unsized type 'opaque'");
|
||||
};
|
||||
let v: i64 = astalign(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
e.type_ = tinfofornode(c, utn): *void;
|
||||
|
||||
@@ -7999,6 +7999,59 @@ fn astsize(c: *checker, t: *node) i64 = {
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// astunsized — #108(b): true iff `t` contains an unsized component. A
|
||||
// type is unsized iff it is the abstract `opaque` (size/align ==
|
||||
// SIZE_UNDEFINED) OR an aggregate (array / struct / tuple / tagged)
|
||||
// with a recursively-unsized member. The wwstage has NO type-decl
|
||||
// construction guards (those are cstage-only, rule-10), so its size()/
|
||||
// align() FOLD must detect every opaque-containing type itself — a
|
||||
// leaf-only check would silently fold size([4]opaque) / size(struct{x:
|
||||
// opaque}) / size((opaque, i32)) to garbage (rule 7). Does NOT peel
|
||||
// TPTR/TSLICE/TCHAN/TFN — `*opaque` (8B) and `[]opaque` (24B header)
|
||||
// are sized and legal behind indirection. Cstage twin: the leaf
|
||||
// `m == SIZE_UNDEFINED` size/align guard PLUS the per-construction
|
||||
// require_sized guards that reject unsized aggregates at the type decl
|
||||
// (so the cstage size/align fold only ever sees a leaf opaque); harec
|
||||
// ref/harec/src/check.c:2720, type_store.c:1147 (tuple) / :449 (tagged).
|
||||
fn astunsized(c: *checker, t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return false; };
|
||||
let k: nkind = u.kind;
|
||||
if (k == nkind.N_TNAME) {
|
||||
if (streq(u.str, "opaque")) { return true; };
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TARRAY) { return astunsized(c, u.lhs); };
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let p: *node = u.list;
|
||||
for (p != nil) {
|
||||
if (astunsized(c, p.lhs)) { return true; };
|
||||
p = p.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TSTRUCT) {
|
||||
let f: *node = u.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
if (astunsized(c, f.lhs)) { return true; };
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_TTAGGED) {
|
||||
let v: *node = u.list;
|
||||
for (v != nil) {
|
||||
if (astunsized(c, v)) { return true; };
|
||||
v = v.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// matchyieldtype — port of cstage cmd/wcc/check.c:110-135. Walks a
|
||||
// match arm body for the first `yield expr;` and returns its operand
|
||||
// type. Returns nil if no yield is reachable from `body`. Doesn't
|
||||
@@ -9183,12 +9236,20 @@ fn exprtype(c: *checker, e: *node, hint: *node) *node = {
|
||||
// own type.
|
||||
let utn: *node = mktname(c, "untyped_int");
|
||||
if (issize) {
|
||||
// #108(b): rule-10 twin of the cstage
|
||||
// size/align unsized guard.
|
||||
if (astunsized(c, e.list)) {
|
||||
deffolderr(c, e, "cannot take size of unsized type 'opaque'");
|
||||
};
|
||||
let v: i64 = astsize(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
e.type_ = tinfofornode(c, utn): *void;
|
||||
return mktname(c, "i32");
|
||||
};
|
||||
if (isalign) {
|
||||
if (astunsized(c, e.list)) {
|
||||
deffolderr(c, e, "cannot take align of unsized type 'opaque'");
|
||||
};
|
||||
let v: i64 = astalign(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
e.type_ = tinfofornode(c, utn): *void;
|
||||
|
||||
Reference in New Issue
Block a user