wcc: &s.len / &s.cap typed as *i64 (closes #13)
TK_AMP early-exit on slice/str .len/.cap pseudo-fields now returns *i64 instead of legacy *i32. Slice ABI is 24B fixed; LEAQ at the slot was already correct, only the pointer typing was wrong — store-width flips from MOVL to MOVQ via the existing primsize-from-tnode path. &s.ptr untouched (already **T).
This commit is contained in:
@@ -623,6 +623,23 @@ cunop(Checker *c, Node *n)
|
||||
type_name(c->a, t));
|
||||
return t->sub;
|
||||
case TK_AMP: /* address-of */
|
||||
/* Slice/str pseudo-fields .len/.cap surface as i32 but live
|
||||
* in 8B-aligned slots in the header (ptr@0, len@8, cap@16).
|
||||
* Address-of must be typed *i64 so deref-write hits the full
|
||||
* slot; otherwise *&s.len = N stores 4B (MOVL) and the upper
|
||||
* 4B leak from whatever the prior MOVQ store of s.len left
|
||||
* behind. */
|
||||
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs &&
|
||||
n->lhs->str &&
|
||||
(strcmp(n->lhs->str, "len") == 0 ||
|
||||
strcmp(n->lhs->str, "cap") == 0)) {
|
||||
Type *bt = n->lhs->lhs->type;
|
||||
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
||||
if (bu && bu->kind == TY_PTR) bu = bu->sub;
|
||||
if (bu && bu->kind == TY_NAMED) bu = bu->under;
|
||||
if (bu && (bu->kind == TY_SLICE || bu->kind == TY_STR))
|
||||
return type_ptr(c->a, ty_i64);
|
||||
}
|
||||
return type_ptr(c->a, t);
|
||||
default:
|
||||
return err(c, n->pos, "unsupported unary %s", tokname(n->op));
|
||||
|
||||
@@ -102,8 +102,10 @@ static const struct row rows[] = {
|
||||
55 },
|
||||
/* The motivating idiom: write through `&s.len` on a slice header
|
||||
* to truncate without re-allocating. Mirrors the Hare slice-
|
||||
* header poke pattern that bufio will eventually want. Test:
|
||||
* fill a slice's len to 5, then *&s.len = 0; assert s.len == 0. */
|
||||
* header poke pattern that bufio will eventually want. The slice
|
||||
* header's .len slot is 8B even though the surface type is i32,
|
||||
* so `&s.len` is `*i64` (matches storage); deref-store hits all
|
||||
* 8B, and the i64 load reads back the value the user wrote. */
|
||||
{ "amp_dot_slice_len_writethrough",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
@@ -111,11 +113,97 @@ static const struct row rows[] = {
|
||||
" s.ptr = &arr[0];\n"
|
||||
" s.len = 4;\n"
|
||||
" s.cap = 4;\n"
|
||||
" let q: *i32 = &s.len;\n"
|
||||
" *q = 0;\n"
|
||||
" let q: *i64 = &s.len;\n"
|
||||
" *q = 0i64;\n"
|
||||
" return s.len: i32;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
/* Width-stress for `&s.len`: write a value whose lower-32B
|
||||
* differs from upper-32B and confirm the upper bytes don't
|
||||
* leak from the prior 8B store of `s.len = 4`. Before the fix,
|
||||
* `&s.len` was `*i32` and `*q = v` lowered to MOVL, leaving
|
||||
* the upper 4B at whatever the MOVQ store of 4 left there
|
||||
* (zero — pass by accident). With a non-zero stale upper or a
|
||||
* fresh write that fills both halves, the i64 read of s.len
|
||||
* exposes the mismatch. We write 0xFFFF_FFFF_FFFF_FFFF and
|
||||
* return 1 iff s.len reads back as -1 (i64). */
|
||||
{ "amp_dot_slice_len_width_stress",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = &arr[0];\n"
|
||||
" s.len = 4;\n"
|
||||
" s.cap = 4;\n"
|
||||
" let q: *i64 = &s.len;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = s.len: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* Same shape for `&s.cap`. cap lives at +16 in the slice header
|
||||
* and was the second pseudo-field broken by the same width bug. */
|
||||
{ "amp_dot_slice_cap_width_stress",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.ptr = &arr[0];\n"
|
||||
" s.len = 4;\n"
|
||||
" s.cap = 4;\n"
|
||||
" let q: *i64 = &s.cap;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = s.cap: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* Same shape for str.len. str header is (ptr, len) with len at
|
||||
* +8, also 8B storage. */
|
||||
{ "amp_dot_str_len_width_stress",
|
||||
"fn main() i32 = {\n"
|
||||
" let s: str = \"abcd\";\n"
|
||||
" let q: *i64 = &s.len;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = s.len: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* `&s.ptr` write-through. ptr lives at +0 and is `*T` (not a
|
||||
* pseudo-i32), so `&s.ptr` types as `**T` — already the right
|
||||
* width pre-fix. Pin it so a future regression doesn't slip the
|
||||
* other way. */
|
||||
{ "amp_dot_slice_ptr_writethrough",
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [2]u8;\n"
|
||||
" arr[0] = 13; arr[1] = 0;\n"
|
||||
" let s: []u8;\n"
|
||||
" s.len = 2; s.cap = 2;\n"
|
||||
" let pp: **u8 = &s.ptr;\n"
|
||||
" *pp = &arr[0];\n"
|
||||
" return s.ptr[0]: i32;\n"
|
||||
"};\n",
|
||||
13 },
|
||||
/* Chained `&w.s.len` — slice WRAPPED in a struct. The pseudo-
|
||||
* field width override gates on the leaf .len/.cap with base
|
||||
* type TY_SLICE/TY_STR; for `&w.s.len` the base of the leaf
|
||||
* dot is `w.s` (type []u8), so the override must still fire
|
||||
* and the deref-store must hit all 8B at the wrapped slice
|
||||
* header's len slot (offset of .s + 8). */
|
||||
{ "amp_dot_slice_field_len_width",
|
||||
"type wrap = struct { s: []u8, x: i32 };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let arr: [4]u8;\n"
|
||||
" let w: wrap;\n"
|
||||
" w.s.ptr = &arr[0];\n"
|
||||
" w.s.len = 4; w.s.cap = 4;\n"
|
||||
" let q: *i64 = &w.s.len;\n"
|
||||
" *q = -1i64;\n"
|
||||
" let v: i64 = w.s.len: i64;\n"
|
||||
" if (v == -1i64) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
1 },
|
||||
/* &o.s.ptr — slice-FIELD of a struct: combines shape 1 (walk
|
||||
* into struct field at .s) and shape 3 (slice pseudo-tail
|
||||
* .ptr at offset 0 of the header). Proves the spine walker's
|
||||
|
||||
Reference in New Issue
Block a user