check: reject an array-literal borrow into a union slice success variant (#13)
A stack array literal returned into a tagged-union slice success variant (fn mk() ([]i32|e) = { return [10,20,30]; }) slipped past reject_arrlit_borrow — it bailed when the dst was not TY_SLICE, but a union dst is TY_TAGGED — so cgen built an all-zeros slice header: a silent both-stage miscompile (and the .ptr would dangle anyway, no outliving backing). Extend the reject to chase a TY_TAGGED dst to its slice success variant, then apply the existing reject; this is the #25/#31 treatment seen through the union, and matches Hare (rule-9). Reached by all acceptance sites (return/assign/call-arg) so the class closes by construction. Both stages converge on an identical accept/reject decision (an array literal assignable to a union is assignable to a slice or array variant; neither stage can accept it). Full support — promoting the literal to an outliving backing — is the separate #33 arc. Compile-error fixture + a positive over-reach guard (a real slice into the union still compiles). No asm emitted by a reject, so byte-id is unchanged (no floor ratchet).
This commit is contained in:
@@ -1192,6 +1192,22 @@ reject_arrlit_borrow(Checker *c, Type *dst, Node *expr)
|
||||
{
|
||||
if (expr == NULL || expr->kind != N_ARRLIT) return 0;
|
||||
Type *du = type_chase_named(dst);
|
||||
/* #13: the borrow target may be a SLICE success variant of a tagged-
|
||||
* union return — the same no-outliving-backing dangle as a bare slice,
|
||||
* but it slips the TY_SLICE gate (c->ret chases to TY_TAGGED). Chase to
|
||||
* the assignable slice variant so the reject sees through the union. An
|
||||
* array-typed variant is the separate #5/#60 reject upstream; full
|
||||
* non-let support (an outliving backing) is #33. */
|
||||
if (du && du->kind == TY_TAGGED) {
|
||||
for (Tparam *p = du->params; p; p = p->next) {
|
||||
Type *pu = type_chase_named(p->type);
|
||||
if (pu && pu->kind == TY_SLICE
|
||||
&& type_assignable(p->type, expr->type)) {
|
||||
du = pu;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (du == NULL || du->kind != TY_SLICE) return 0;
|
||||
err(c, expr->pos, "array literal cannot borrow as a slice here; "
|
||||
"bind it to a `let` first");
|
||||
|
||||
@@ -5607,6 +5607,23 @@ fn rejectarrlitborrow(c: *checker, dsttn: *syntax.node, val: *syntax.node) bool
|
||||
if (val.kind != syntax.nkind.N_ARRLIT) { return false; };
|
||||
let du: *syntax.node = resolvealias(c, unwrapbang(dsttn));
|
||||
if (du == nil) { return false; };
|
||||
// #13: the borrow target may be a SLICE success variant of a tagged-
|
||||
// union return — same no-outliving-backing dangle, but it slips the
|
||||
// N_TSLICE gate (the dst node chases to N_TTAGGED). Chase to a slice
|
||||
// variant so the reject sees through the union. An N_ARRLIT can only
|
||||
// target a slice variant; an array-typed variant is the #5/#60 reject
|
||||
// upstream; full non-let support is #33. Twin of cstage #13 arm.
|
||||
if (du.kind == syntax.nkind.N_TTAGGED) {
|
||||
let v: *syntax.node = du.list;
|
||||
for (v != nil) {
|
||||
let vu: *syntax.node = resolvealias(c, unwrapbang(v));
|
||||
if (vu != nil && vu.kind == syntax.nkind.N_TSLICE) {
|
||||
du = vu;
|
||||
break;
|
||||
};
|
||||
v = v.next;
|
||||
};
|
||||
};
|
||||
if (du.kind != syntax.nkind.N_TSLICE) { return false; };
|
||||
let m: str = "array literal cannot borrow as a slice here; bind it to a `let` first\n";
|
||||
cerr(m);
|
||||
|
||||
14
test/wcc/data/arrlit_slice_reject_union_ret/case.ww
Normal file
14
test/wcc/data/arrlit_slice_reject_union_ret/case.ww
Normal file
@@ -0,0 +1,14 @@
|
||||
//ww:error "cannot borrow as a slice here"
|
||||
// #13 carrier: an array LITERAL returned into a tagged-union SLICE success
|
||||
// variant ([]i32 | e) has no outliving backing (the maker's frame dies) — the
|
||||
// .ptr would dangle, the same #31/#33 no-backing reject as a bare-slice return.
|
||||
// Both stages must REJECT: the borrow gate now sees THROUGH the union to its
|
||||
// []i32 success variant (the silent all-zeros-header miscompile gap). Full
|
||||
// non-let support (an outliving backing) is #33.
|
||||
package main;
|
||||
type e = !i32;
|
||||
fn mk() ([]i32 | e) = { return [10i32, 20i32, 30i32]; };
|
||||
export fn main() i32 = {
|
||||
let s: []i32 = mk()!;
|
||||
return s[0];
|
||||
};
|
||||
14
test/wcc/data/arrlit_slice_union_ok/case.ww
Normal file
14
test/wcc/data/arrlit_slice_union_ok/case.ww
Normal file
@@ -0,0 +1,14 @@
|
||||
//ww:run-exit 10
|
||||
// #13 over-reach guard: a REAL slice returned into the same tagged-union
|
||||
// success variant ([]i32 | e) has a live backing (the caller's array `a`), so
|
||||
// both stages must still ACCEPT and build the correct header (tag=0, byte-id).
|
||||
// Discriminates the array-LITERAL reject (sibling carrier) from a blanket
|
||||
// union-slice-return reject — proves the #13 fix is surgical. Returns s[0]=10.
|
||||
package main;
|
||||
type e = !i32;
|
||||
fn mk(src: []i32) ([]i32 | e) = { return src; };
|
||||
export fn main() i32 = {
|
||||
let a: [3]i32 = [10i32, 20i32, 30i32];
|
||||
let s: []i32 = mk(a)!;
|
||||
return s[0];
|
||||
};
|
||||
Reference in New Issue
Block a user