Every section banner dies (103 -> 0) across test/lang, the observer suites, the C carriers, and the five comment-heavy corpus fixtures; banner provenance (#N cites, carrier numbers, repair-cluster labels) folded into headers or adjacent WHY comments. Narration deleted; row provenance, ref cites, divergence pins, and layout contracts kept (fwd-ref decl-order guards and bootstrap-gate corpus rationale restored where the sweep over-cut). Comment-only proven: all 3742 wwbuild workdir .s byte-identical before/after; test-commit and test-byteid (161 lang + 1399 data, 0 pinned-divergent) green.
65 lines
2.8 KiB
Plaintext
65 lines
2.8 KiB
Plaintext
// Runtime contract for a whole 24B str/slice header
|
|
// passed BY VALUE as a call argument whose SOURCE is a deref (`f(*h)` with
|
|
// h:*str / h:*[]T). #9 (C1c): cgenutil.ww's nodeisslice/nodeisstr had no
|
|
// N_UN(TK_STAR) arm, so `*h` fell through to the scalar single-PUSHQ default
|
|
// — pushargsrev marshalled only .ptr (1 word) and cgcall's pop sizer drained
|
|
// one word, so the callee read .len/.cap from stale arg registers. C1b
|
|
// (c67f362) already fixed the 24B header LOAD; this pins the call-arg push
|
|
// COUNT. cstage (cmd/w6c/cgen.c node_isslice/node_isstr) was always type-
|
|
// keyed and correct; this aligns wwstage UP.
|
|
//
|
|
// WHY the poison call: a pre-fix `f(*h)` pushes only .ptr, so the callee
|
|
// reads .len from whatever arg register the caller left — NOT from the
|
|
// deref load (which lands .len in BX, a reg the callee never reads). A bare
|
|
// decoy header does not survive into that register, so the bug can hide
|
|
// behind a residue coincidence (observed: a naive str case falsely PASSED).
|
|
// The preceding NORMAL-arg call f(decoy) deterministically leaves the
|
|
// callee's .len register holding decoy's len (≠ backing's), so a pre-fix
|
|
// f(*h) reads the decoy's len and the assert REDDENS. Verified
|
|
// reddens-on-revert: with the N_UN arm removed both fns RED, with it
|
|
// restored both GREEN. byte-id (990-996) proves the two stages AGREE, not
|
|
// that the code is correct — this gap was byte-id-identical on both stages.
|
|
|
|
package deref_callarg_test;
|
|
|
|
fn slclen(s: []i32) i32 = { return s.len: i32; };
|
|
|
|
fn slccap(s: []i32) i32 = { return s.cap: i32; };
|
|
|
|
fn slcsum(s: []i32) i32 = { return s[0] + s[1]; };
|
|
|
|
fn strlen(s: str) i32 = { return s.len: i32; };
|
|
|
|
fn strcap(s: str) i32 = { return s.cap: i32; };
|
|
|
|
fn strfirst(s: str) i32 = { return s[0]: i32; };
|
|
|
|
@test fn slice_deref_callarg() void = {
|
|
let backing: []i32 = [10i32, 20i32, 30i32, 40i32];
|
|
let h: *[]i32 = &backing;
|
|
let decoy: []i32 = [1i32];
|
|
// Poison the callee's .len register with decoy's 1 via a normal-arg
|
|
// call, then the deref-call must overwrite it with backing's 4.
|
|
assert(slclen(decoy) == 1);
|
|
assert(slclen(*h) == 4);
|
|
// .cap is the 3rd dropped word; decoy ([1]) seeds cap 1, so a pre-fix
|
|
// 1-word push leaves the callee reading 1 here, not backing's 4.
|
|
assert(slccap(*h) == 4);
|
|
// slcsum pins .ptr survived the push (10 + 20).
|
|
assert(slcsum(*h) == 30);
|
|
};
|
|
|
|
@test fn str_deref_callarg() void = {
|
|
// str IS []u8 — same 3-word arg. Decoy ("xy", len 2) poisons the
|
|
// callee's .len register so a pre-fix strlen(*sp) reads 2, not 5.
|
|
let backing: str = "abcde";
|
|
let sp: *str = &backing;
|
|
let decoy: str = "xy";
|
|
assert(strlen(decoy) == 2);
|
|
assert(strlen(*sp) == 5);
|
|
// .cap twin of slccap; decoy ("xy") seeds cap 2, not backing's 5.
|
|
assert(strcap(*sp) == 5);
|
|
// strfirst pins .ptr survived the push ('a' == 97).
|
|
assert(strfirst(*sp) == 97);
|
|
};
|