#!/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: ` # 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 = "\\]+\"[ \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