tools/sizelint + CLAUDE.md rule 13: gate hardcoded size literals

Drew's Hare-discipline framing: "no hardcoded size literals anywhere in
the compiler." This session spent 32 commits sweeping after-the-fact
and STILL kept introducing new bypass sites in our own structural
work (A.5's tupleelemslot/fieldslotsize most recently). The cure is a
gate that catches new violations at commit time, not a deeper sweep.

tools/sizelint (sh+gawk):
- Always-on: `.size = NN` / `->size = NN` / `prim(...,"name",NN,...)`.
- Context-gated literals (NN(u64|i64) and `return NN`) in files or fns
  matching size|slot|elem|field|stride|paramfield|tinfo|primtype|
  slotsize|letemit|tagged.
- Allow-list via `// sizelint-ok: <reason>` or `/* sizelint-ok: ... */`.
- Comment strip happens after allow-list match so prose mentions of
  16/24 stay quiet.

Makefile: `test: all sizelint $(TESTS)` so the gate runs before any
binary builds.

CLAUDE.md rule 13 documents the discipline + escape hatch + optional
pre-commit-hook symlink.

Audit caught 3 real cstage bugs (cmd/wcc/check.c resolve_type:1002,
1079, 1531 hardcoded `tt->size = 16` / `= 32` for tagged-with-ptr and
tagged-with-slice payloads — should read `8 + sub.size`). Fixed
inline; behavioral no-op today (pt->size=16, st->size=24, sub.size=24
match the prior literals) but the SSoT seam carries forward through
#1/#34/#65.

8 SSoT-seed allow-lists added (cstage type.c ty_str/ty_slice prim
factories; wwstage primtypesize/tyslicesize; lib/ww/typ.ww tystr +
slice fields + their main.combined.ww mirrors). One amalloc-overalloc
allow-list at lib/ww/typ.ww:273 cites pending #36 (typed amalloc).

#66 filed for extending the filter once #65 routes lib/bytes +
lib/getopt's sizeof(slice) / sizeof(option) literals through SSoT —
naive line-pattern extension would false-positive on 22+ ELF wire-
format sites in dynout.ww.

131/131 + 994 + 995 + bootstrap green with `make sizelint` exit 0.
This commit is contained in:
2026-05-20 15:22:21 +09:00
parent 03b7336cae
commit f80927201b
9 changed files with 162 additions and 25 deletions

View File

@@ -186,7 +186,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
c.tyf64 = prim(a, tykind.TY_F64, "f64", 8u64, 8u64);
c.tystr = prim(a, tykind.TY_STR, "str", 16u64, 8u64);
c.tystr = prim(a, tykind.TY_STR, "str", 16u64, 8u64); // sizelint-ok: SSoT for tystr (#64)
c.tyerr = prim(a, tykind.TY_ERR, "<err>", 0u64, 1u64);
c.tynever = prim(a, tykind.TY_NEVER, "never", 0u64, 1u64);
@@ -210,9 +210,9 @@ export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
let t: *tinfo = newtype(a, tykind.TY_SLICE);
t.sub = sub;
t.size = 24u64;
t.size = 24u64; // sizelint-ok: SSoT for slice header (#64)
t.align = 8u64;
t.slotsize = 24u64;
t.slotsize = 24u64; // sizelint-ok: SSoT for slice slotsize (#64)
return t;
};
@@ -270,7 +270,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
};
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
let e: *tinfocacheent = amalloc(c.a, 32u64): *tinfocacheent;
// #36 (typed amalloc) — over-size the 24B tinfocacheent struct to
// dodge the cstage amalloc<size silent corruption (selfhost/CLAUDE.md).
let e: *tinfocacheent = amalloc(c.a, 32u64): *tinfocacheent; // sizelint-ok: amalloc over-size pending #36
e.key = key;
e.val = val;
e.cnext = c.tinfocache;