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:
@@ -10,3 +10,4 @@
|
|||||||
10. Symmetric stages. cstage and wwstage MUST emit byte-identical asm for the same input. When inference power differs, align the richer side DOWN to the leaner side, not the other way.
|
10. Symmetric stages. cstage and wwstage MUST emit byte-identical asm for the same input. When inference power differs, align the richer side DOWN to the leaner side, not the other way.
|
||||||
11. Split commits when they bundle unrelated concerns. Bisect-cleanliness is the default. Multi-fix commits need a body paragraph explaining why they couldn't split.
|
11. Split commits when they bundle unrelated concerns. Bisect-cleanliness is the default. Multi-fix commits need a body paragraph explaining why they couldn't split.
|
||||||
12. Simple data, simple algorithms. Sea-of-stars style. Mirror Hare's structural choices over clever alternatives.
|
12. Simple data, simple algorithms. Sea-of-stars style. Mirror Hare's structural choices over clever alternatives.
|
||||||
|
13. No hardcoded size literals in size-computation contexts. Always route through the type table (tinfo.size / Type.size / ty_*->size / size(T) / primtypesize / tyslicesize). Per Drew's framing of Hare's discipline. `make sizelint` enforces and runs as a dep of `make test`. Exemptions documented inline with `// sizelint-ok: <reason>` (ww) or `/* sizelint-ok: <reason> */` (C). Optional local enforcement: `ln -s ../../tools/sizelint .git/hooks/pre-commit`.
|
||||||
|
|||||||
7
Makefile
7
Makefile
@@ -926,7 +926,10 @@ $(BIN)/test_random_run: test/wcc/999_random_run.c $(BIN)/ww $(BIN)/w6c \
|
|||||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
test: all $(TESTS)
|
sizelint:
|
||||||
|
@sh tools/sizelint
|
||||||
|
|
||||||
|
test: all sizelint $(TESTS)
|
||||||
@WW=$(BIN)/ww BIN=$(BIN) sh test/run
|
@WW=$(BIN)/ww BIN=$(BIN) sh test/run
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
@@ -1074,4 +1077,4 @@ nocc:
|
|||||||
@echo
|
@echo
|
||||||
@echo "NOCC OK: $(STAGE0)/* reproduces itself from source. cc not invoked."
|
@echo "NOCC OK: $(STAGE0)/* reproduces itself from source. cc not invoked."
|
||||||
|
|
||||||
.PHONY: all cstage wwstage test install clean bootstrap nocc bootstrap-snapshot
|
.PHONY: all cstage wwstage test sizelint install clean bootstrap nocc bootstrap-snapshot
|
||||||
|
|||||||
@@ -998,7 +998,8 @@ cexpr(Checker *c, Node *n)
|
|||||||
vp->type = pt; vp->next = ve;
|
vp->type = pt; vp->next = ve;
|
||||||
ve->type = ty_nomem; ve->next = NULL;
|
ve->type = ty_nomem; ve->next = NULL;
|
||||||
tt->params = vp;
|
tt->params = vp;
|
||||||
tt->size = 16;
|
/* #64: tag (8) + max-variant payload, per resolve_type:433. */
|
||||||
|
tt->size = 8 + pt->size;
|
||||||
tt->align = 8;
|
tt->align = 8;
|
||||||
n->type = tt;
|
n->type = tt;
|
||||||
n->lhs->type = ty_err;
|
n->lhs->type = ty_err;
|
||||||
@@ -1074,7 +1075,8 @@ cexpr(Checker *c, Node *n)
|
|||||||
vs->type = st; vs->next = ve;
|
vs->type = st; vs->next = ve;
|
||||||
ve->type = ty_nomem; ve->next = NULL;
|
ve->type = ty_nomem; ve->next = NULL;
|
||||||
tt->params = vs;
|
tt->params = vs;
|
||||||
tt->size = 32;
|
/* #64: tag (8) + slice payload, per resolve_type:433. */
|
||||||
|
tt->size = 8 + st->size;
|
||||||
tt->align = 8;
|
tt->align = 8;
|
||||||
n->type = tt;
|
n->type = tt;
|
||||||
n->lhs->type = ty_err;
|
n->lhs->type = ty_err;
|
||||||
@@ -1525,7 +1527,8 @@ clet(Checker *c, Node *n)
|
|||||||
vs->type = st; vs->next = ve;
|
vs->type = st; vs->next = ve;
|
||||||
ve->type = ty_nomem; ve->next = NULL;
|
ve->type = ty_nomem; ve->next = NULL;
|
||||||
tt->params = vs;
|
tt->params = vs;
|
||||||
tt->size = 32;
|
/* #64: tag (8) + slice payload, per resolve_type:433. */
|
||||||
|
tt->size = 8 + st->size;
|
||||||
tt->align = 8;
|
tt->align = 8;
|
||||||
call->type = tt;
|
call->type = tt;
|
||||||
if (wrap) {
|
if (wrap) {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ typesinit(Arena *a)
|
|||||||
ty_f32 = prim(a, TY_F32, "f32", 4, 4);
|
ty_f32 = prim(a, TY_F32, "f32", 4, 4);
|
||||||
ty_f64 = prim(a, TY_F64, "f64", 8, 8);
|
ty_f64 = prim(a, TY_F64, "f64", 8, 8);
|
||||||
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
||||||
ty_str = prim(a, TY_STR, "str", 16, 8);
|
ty_str = prim(a, TY_STR, "str", 16, 8); /* sizelint-ok: SSoT for ty_str (#64) */
|
||||||
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
||||||
ty_never = prim(a, TY_NEVER, "never", 0, 1);
|
ty_never = prim(a, TY_NEVER, "never", 0, 1);
|
||||||
/* #29: predeclared `type nomem = !void;`. NAMED so variant_match
|
/* #29: predeclared `type nomem = !void;`. NAMED so variant_match
|
||||||
@@ -98,7 +98,7 @@ type_slice(Arena *a, Type *sub)
|
|||||||
{
|
{
|
||||||
Type *t = newtype(a, TY_SLICE);
|
Type *t = newtype(a, TY_SLICE);
|
||||||
t->sub = sub;
|
t->sub = sub;
|
||||||
t->size = 24; /* { *T, len, cap } */
|
t->size = 24; /* { *T, len, cap } */ /* sizelint-ok: SSoT for ty_slice (#64) */
|
||||||
t->align = 8;
|
t->align = 8;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
|
|||||||
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
||||||
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
|
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
|
||||||
c.tyf64 = prim(a, tykind.TY_F64, "f64", 8u64, 8u64);
|
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.tyerr = prim(a, tykind.TY_ERR, "<err>", 0u64, 1u64);
|
||||||
c.tynever = prim(a, tykind.TY_NEVER, "never", 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 = {
|
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
||||||
let t: *tinfo = newtype(a, tykind.TY_SLICE);
|
let t: *tinfo = newtype(a, tykind.TY_SLICE);
|
||||||
t.sub = sub;
|
t.sub = sub;
|
||||||
t.size = 24u64;
|
t.size = 24u64; // sizelint-ok: SSoT for slice header (#64)
|
||||||
t.align = 8u64;
|
t.align = 8u64;
|
||||||
t.slotsize = 24u64;
|
t.slotsize = 24u64; // sizelint-ok: SSoT for slice slotsize (#64)
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -270,7 +270,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
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.key = key;
|
||||||
e.val = val;
|
e.val = val;
|
||||||
e.cnext = c.tinfocache;
|
e.cnext = c.tinfocache;
|
||||||
|
|||||||
@@ -6613,7 +6613,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
|
|||||||
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
||||||
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
|
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
|
||||||
c.tyf64 = prim(a, tykind.TY_F64, "f64", 8u64, 8u64);
|
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.tyerr = prim(a, tykind.TY_ERR, "<err>", 0u64, 1u64);
|
||||||
c.tynever = prim(a, tykind.TY_NEVER, "never", 0u64, 1u64);
|
c.tynever = prim(a, tykind.TY_NEVER, "never", 0u64, 1u64);
|
||||||
|
|
||||||
@@ -6637,9 +6637,9 @@ export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
|
|||||||
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
||||||
let t: *tinfo = newtype(a, tykind.TY_SLICE);
|
let t: *tinfo = newtype(a, tykind.TY_SLICE);
|
||||||
t.sub = sub;
|
t.sub = sub;
|
||||||
t.size = 24u64;
|
t.size = 24u64; // sizelint-ok: SSoT for slice header (#64)
|
||||||
t.align = 8u64;
|
t.align = 8u64;
|
||||||
t.slotsize = 24u64;
|
t.slotsize = 24u64; // sizelint-ok: SSoT for slice slotsize (#64)
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6697,7 +6697,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
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.key = key;
|
||||||
e.val = val;
|
e.val = val;
|
||||||
e.cnext = c.tinfocache;
|
e.cnext = c.tinfocache;
|
||||||
@@ -7757,14 +7759,14 @@ fn primtypesize(nm: str) i64 = {
|
|||||||
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
||||||
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64")) { return 8i64; };
|
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64")) { return 8i64; };
|
||||||
if (streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
if (streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
||||||
if (streq(nm, "str")) { return 16i64; };
|
if (streq(nm, "str")) { return 16i64; }; // sizelint-ok: SSoT for ty_str primtype (#64)
|
||||||
return -1i64;
|
return -1i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// #43: SSoT for slice header size (ptr+len+cap = 24B today). Mirrors
|
// #43: SSoT for slice header size (ptr+len+cap = 24B today). Mirrors
|
||||||
// cstage cmd/wcc/type.c:103 (ty_slice->size = 24). Bumping a slice's
|
// cstage cmd/wcc/type.c:103 (ty_slice->size = 24). Bumping a slice's
|
||||||
// header layout in #34 touches only this constant.
|
// header layout in #34 touches only this constant.
|
||||||
fn tyslicesize() i64 = { return 24i64; };
|
fn tyslicesize() i64 = { return 24i64; }; // sizelint-ok: SSoT for ty_slice header (#64)
|
||||||
|
|
||||||
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
||||||
// fold. Mirror cstage resolve_type's size/align computation
|
// fold. Mirror cstage resolve_type's size/align computation
|
||||||
|
|||||||
@@ -670,14 +670,14 @@ fn primtypesize(nm: str) i64 = {
|
|||||||
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
||||||
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64")) { return 8i64; };
|
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64")) { return 8i64; };
|
||||||
if (streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
if (streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
||||||
if (streq(nm, "str")) { return 16i64; };
|
if (streq(nm, "str")) { return 16i64; }; // sizelint-ok: SSoT for ty_str primtype (#64)
|
||||||
return -1i64;
|
return -1i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// #43: SSoT for slice header size (ptr+len+cap = 24B today). Mirrors
|
// #43: SSoT for slice header size (ptr+len+cap = 24B today). Mirrors
|
||||||
// cstage cmd/wcc/type.c:103 (ty_slice->size = 24). Bumping a slice's
|
// cstage cmd/wcc/type.c:103 (ty_slice->size = 24). Bumping a slice's
|
||||||
// header layout in #34 touches only this constant.
|
// header layout in #34 touches only this constant.
|
||||||
fn tyslicesize() i64 = { return 24i64; };
|
fn tyslicesize() i64 = { return 24i64; }; // sizelint-ok: SSoT for ty_slice header (#64)
|
||||||
|
|
||||||
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
||||||
// fold. Mirror cstage resolve_type's size/align computation
|
// fold. Mirror cstage resolve_type's size/align computation
|
||||||
|
|||||||
@@ -6613,7 +6613,7 @@ export fn typesinit(c: *tctx, a: *arena) void = {
|
|||||||
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
||||||
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
|
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
|
||||||
c.tyf64 = prim(a, tykind.TY_F64, "f64", 8u64, 8u64);
|
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.tyerr = prim(a, tykind.TY_ERR, "<err>", 0u64, 1u64);
|
||||||
c.tynever = prim(a, tykind.TY_NEVER, "never", 0u64, 1u64);
|
c.tynever = prim(a, tykind.TY_NEVER, "never", 0u64, 1u64);
|
||||||
|
|
||||||
@@ -6637,9 +6637,9 @@ export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
|
|||||||
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
|
||||||
let t: *tinfo = newtype(a, tykind.TY_SLICE);
|
let t: *tinfo = newtype(a, tykind.TY_SLICE);
|
||||||
t.sub = sub;
|
t.sub = sub;
|
||||||
t.size = 24u64;
|
t.size = 24u64; // sizelint-ok: SSoT for slice header (#64)
|
||||||
t.align = 8u64;
|
t.align = 8u64;
|
||||||
t.slotsize = 24u64;
|
t.slotsize = 24u64; // sizelint-ok: SSoT for slice slotsize (#64)
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6697,7 +6697,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
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.key = key;
|
||||||
e.val = val;
|
e.val = val;
|
||||||
e.cnext = c.tinfocache;
|
e.cnext = c.tinfocache;
|
||||||
@@ -7757,14 +7759,14 @@ fn primtypesize(nm: str) i64 = {
|
|||||||
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
||||||
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64")) { return 8i64; };
|
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64")) { return 8i64; };
|
||||||
if (streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
if (streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
||||||
if (streq(nm, "str")) { return 16i64; };
|
if (streq(nm, "str")) { return 16i64; }; // sizelint-ok: SSoT for ty_str primtype (#64)
|
||||||
return -1i64;
|
return -1i64;
|
||||||
};
|
};
|
||||||
|
|
||||||
// #43: SSoT for slice header size (ptr+len+cap = 24B today). Mirrors
|
// #43: SSoT for slice header size (ptr+len+cap = 24B today). Mirrors
|
||||||
// cstage cmd/wcc/type.c:103 (ty_slice->size = 24). Bumping a slice's
|
// cstage cmd/wcc/type.c:103 (ty_slice->size = 24). Bumping a slice's
|
||||||
// header layout in #34 touches only this constant.
|
// header layout in #34 touches only this constant.
|
||||||
fn tyslicesize() i64 = { return 24i64; };
|
fn tyslicesize() i64 = { return 24i64; }; // sizelint-ok: SSoT for ty_slice header (#64)
|
||||||
|
|
||||||
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
||||||
// fold. Mirror cstage resolve_type's size/align computation
|
// fold. Mirror cstage resolve_type's size/align computation
|
||||||
|
|||||||
124
tools/sizelint
Executable file
124
tools/sizelint
Executable file
@@ -0,0 +1,124 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# tools/sizelint — gate against hardcoded size literals in size-computation
|
||||||
|
# contexts. Per Drew's framing of Hare's discipline and ww task #64:
|
||||||
|
# every byte size that names a type's footprint must route through the
|
||||||
|
# type table (tinfo.size / Type.size / ty_*->size / size(T)). Bare
|
||||||
|
# numerics encode the layout twice and silently desync (#1, #43, #60,
|
||||||
|
# #65 sweeps caught one site at a time post-hoc).
|
||||||
|
#
|
||||||
|
# Patterns are matched in two tiers:
|
||||||
|
# 1) Always-on strong signals — direct writes to a type's size /
|
||||||
|
# slotsize / align field, the `prim(...,size,align)` factory call,
|
||||||
|
# and any literal in the rhs of `*->size = ` style assignments.
|
||||||
|
# 2) Context-gated literals — bare 16/24/32 (suffix-tagged or not) only
|
||||||
|
# inside files or functions whose name matches one of:
|
||||||
|
# size|slot|elem|field|stride|paramfield|tinfo|primtype|slotsize|
|
||||||
|
# letemit|tagged
|
||||||
|
#
|
||||||
|
# Exempt a single line with an end-of-line `// sizelint-ok: <reason>`
|
||||||
|
# comment. Use sparingly and cite a task or structural reason.
|
||||||
|
#
|
||||||
|
# Scope: cmd/ selfhost/ lib/ (skip ref/ out/ bootstrap/ .combined.ww).
|
||||||
|
# Exit code: 0 if clean, 1 with one diagnostic per violation.
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
ROOT=${ROOT:-$(cd "$(dirname "$0")/.." && pwd)}
|
||||||
|
cd "$ROOT"
|
||||||
|
|
||||||
|
files=$(find cmd selfhost lib \
|
||||||
|
\( -type d -name out -prune \) -o \
|
||||||
|
\( -type d -name bootstrap -prune \) -o \
|
||||||
|
\( -type d -name ref -prune \) -o \
|
||||||
|
\( -type d -name .git -prune \) -o \
|
||||||
|
\( -type d -name .claude -prune \) -o \
|
||||||
|
\( -type d -name .ai -prune \) -o \
|
||||||
|
\( -type f \( -name '*.c' -o -name '*.h' -o -name '*.ww' \) \
|
||||||
|
! -name '*.combined.ww' -print \) )
|
||||||
|
|
||||||
|
exec awk '
|
||||||
|
BEGIN {
|
||||||
|
ctx_re = "size|slot|elem|field|stride|paramfield|tinfo|primtype|slotsize|letemit|tagged"
|
||||||
|
tagged_lit_re = "(16|24|32)(u64|i64)"
|
||||||
|
return_lit_re = "return[ \t]+(16|24|32)[ \t]*[;}]"
|
||||||
|
# Strong signal: assignment to .size / ->size / .slotsize / ->slotsize.
|
||||||
|
# Value 8 is the natural pointer/scalar size — leave un-flagged so
|
||||||
|
# typeptr/typechan stay quiet without an allow-list per assignment.
|
||||||
|
size_assign_re = "(\\.|->)[ \t]*(size|slotsize)[ \t]*=[ \t]*(16|24|32)([^0-9]|$)"
|
||||||
|
# Strong signal: type-table factory `prim(arena, kind, "name", SIZE, ALIGN)`.
|
||||||
|
prim_call_re = "\\<prim[ \t]*\\([^)]*\"[A-Za-z_<>]+\"[ \t]*,[ \t]*(16|24|32)"
|
||||||
|
nviol = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
FNR == 1 {
|
||||||
|
cur_file = FILENAME
|
||||||
|
file_in_ctx = (tolower(cur_file) ~ ctx_re)
|
||||||
|
is_c = (cur_file ~ /\.(c|h)$/)
|
||||||
|
fn_name = ""
|
||||||
|
fn_in_ctx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# Track function context (ww + C styles).
|
||||||
|
{
|
||||||
|
if (match($0, /^[ \t]*(export[ \t]+)?fn[ \t]+[A-Za-z_][A-Za-z0-9_]*/)) {
|
||||||
|
s = substr($0, RSTART, RLENGTH)
|
||||||
|
sub(/^[ \t]*(export[ \t]+)?fn[ \t]+/, "", s)
|
||||||
|
fn_name = s
|
||||||
|
fn_in_ctx = (tolower(fn_name) ~ ctx_re)
|
||||||
|
} else if (is_c && match($0, /^[A-Za-z_][A-Za-z0-9_]*\(/)) {
|
||||||
|
s = substr($0, RSTART, RLENGTH - 1)
|
||||||
|
fn_name = s
|
||||||
|
fn_in_ctx = (tolower(fn_name) ~ ctx_re)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allow-list (case-insensitive on the keyword) — check the raw line so a
|
||||||
|
# doc comment can still exempt itself. Accepts both `// sizelint-ok:` (ww
|
||||||
|
# and C99) and `/* sizelint-ok: ... */` (Plan 9 C style).
|
||||||
|
tolower($0) ~ /sizelint-ok:/ { next }
|
||||||
|
|
||||||
|
# Strip line and block comments before pattern matching — a comment that
|
||||||
|
# quotes a hardcoded size in prose is not a violation. Single-line block
|
||||||
|
# comments only; multi-line `/* ... */` spans rarely contain assignments
|
||||||
|
# we care about.
|
||||||
|
{
|
||||||
|
code = $0
|
||||||
|
sub(/\/\/.*$/, "", code)
|
||||||
|
gsub(/\/\*[^*]*\*+([^/*][^*]*\*+)*\//, "", code)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Always-on strong-signal patterns.
|
||||||
|
{
|
||||||
|
if (match(code, size_assign_re)) {
|
||||||
|
report(substr(code, RSTART, RLENGTH),
|
||||||
|
"assignment to .size/.slotsize with literal; route via type SSoT (tinfo.size / ty_*->size)")
|
||||||
|
}
|
||||||
|
if (match(code, prim_call_re)) {
|
||||||
|
report(substr(code, RSTART, RLENGTH),
|
||||||
|
"literal in prim(...) size slot; type-table is SSoT (cmd/wcc/type.c, lib/ww/typ.ww)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Context-gated patterns.
|
||||||
|
file_in_ctx || fn_in_ctx {
|
||||||
|
line = code
|
||||||
|
while (match(line, tagged_lit_re)) {
|
||||||
|
report(substr(line, RSTART, RLENGTH),
|
||||||
|
"size literal in size-context; route via size(T) / primtypesize / tyslicesize SSoT")
|
||||||
|
line = substr(line, RSTART + RLENGTH)
|
||||||
|
}
|
||||||
|
if (match(code, return_lit_re)) {
|
||||||
|
report(substr(code, RSTART, RLENGTH),
|
||||||
|
"return of bare size literal in size-context; route via type SSoT")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function report(snip, msg) {
|
||||||
|
# Trim trailing newline / extra whitespace from snippet.
|
||||||
|
gsub(/[ \t]+$/, "", snip)
|
||||||
|
printf("%s:%d: %s; suggest: %s\n", cur_file, FNR, snip, msg)
|
||||||
|
nviol++
|
||||||
|
}
|
||||||
|
|
||||||
|
END { exit (nviol > 0 ? 1 : 0) }
|
||||||
|
' $files
|
||||||
Reference in New Issue
Block a user