#!/bin/sh # tools/peellint — gate against raw TY_NAMED single-peel reads outside # the chase accessors. The #5 alias-arc close-by-construction contract # (rob F2/B7 rulings): one chased accessor is the only spelled way to # dealias — type_chase_named (cmd/wcc/type.c) on the C side, tichase # (selfhost/cmd/wcc/cgenutil.ww) on the ww side. A raw `->under` / # `.under` read peels exactly one NAMED layer; chain-of-aliases stacks # two, so every kind-gated consumer downstream of a single peel falls # to a scalar shape (the four-times-burned family: #60/#61/#62/#70…). # ZERO raw under-token reads may exist in scope outside the annotated # whitelist; this gate lands in the same commit that deletes the last # raw read and keeps the class unwritable. # # What this does NOT close (stated honestly, per the f2-ruling): a # consumer that never spells `under` at all — a switch on t->kind that # simply never peels — has no token to see here. That NO-PEEL class is # closed only where classification routes through the internalized # chasing helpers, and contained elsewhere by the acceptance-commit- # carries-tripwires doctrine. # # Matcher: a character scan strips block/line comments and string/char # literals first (a regex pass mis-nests `/*` inside a string — review # probe E6), then the under-token is matched accessor-spelling-wide: # `->under`/`.under` in C (deref-dot `(*t).under` is the same peel), # `.under` in ww, with optional whitespace after the operator and a # line-split continuation (`t->` at EOL, `under` next line) — all the # compiling spellings the B7 review probes evaded with. Right token # bound keeps prose like "io.underread" (check.ww) out. # # Exemption: a line containing `peel-ok` (or the equivalent landed # spelling `peellint-ok`) exempts itself and the following 9 lines — # wide enough that one annotation atop a short construction/chase body # covers it, narrow enough that a stray peel can't hide behind a # distant annotation. Reasons stay WHY-only (rule 8): construction, # chase body, recursive chase, resolve-state probe, or a cited task. # # Scope: cmd/wcc cmd/w6c selfhost/cmd/wcc lib/ww (skip *.combined.ww). # lib/ww/typ.ww is in scope deliberately — it is type.c's ww mirror, # the accessor/classifier layer itself (B7 ruling: excluding it leaves # an unwatched file where the forbidden shape could be written). # Exit code: 0 if clean, 1 with one diagnostic per violation. set -u ROOT=${ROOT:-$(cd "$(dirname "$0")/.." && pwd)} cd "$ROOT" dirs= for d in cmd/wcc cmd/w6c selfhost/cmd/wcc lib/ww; do [ -d "$d" ] && dirs="$dirs $d" done [ -z "$dirs" ] && exit 0 files=$(find $dirs \ \( -type f \( -name '*.c' -o -name '*.h' -o -name '*.ww' \) \ ! -name '*.combined.ww' -print \) ) [ -z "$files" ] && exit 0 exec awk -v sq="'" ' BEGIN { nviol = 0 } FNR == 1 { cur_file = FILENAME is_c = (cur_file ~ /\.(c|h)$/) exempt_until = 0 in_block = 0 pending = 0 } # Whitelist annotation: arm the exemption window on the RAW line so an # annotation inside a comment still counts. `peellint-ok` is the # already-landed sibling spelling (check.ww construction) — accepted # as-is, history is not re-spelled. tolower($0) ~ /peel(lint)?-ok([^a-z0-9_]|$)/ { if (FNR + 9 > exempt_until) exempt_until = FNR + 9 } # Strip comments and string/char literals by character scan: a comment # opener inside a string is not a comment (E6), and literal text is # never code. in_block carries across lines; strings/chars do not. { code = "" n = length($0) i = 1 in_str = 0; in_chr = 0 while (i <= n) { c = substr($0, i, 1) c2 = substr($0, i, 2) if (in_block) { if (c2 == "*/") { in_block = 0; i += 2 } else i++ continue } if (in_str) { if (c == "\\") i += 2 else { if (c == "\"") in_str = 0; i++ } continue } if (in_chr) { if (c == "\\") i += 2 else { if (c == sq) in_chr = 0; i++ } continue } if (c2 == "//") break if (c2 == "/*") { in_block = 1; i += 2; continue } if (c == "\"") { in_str = 1; i++; continue } if (c == sq) { in_chr = 1; i++; continue } code = code c i++ } } { if (FNR <= exempt_until) { pending = 0; next } # Both C spellings peel: p->under and (*p).under / v.under. if (is_c) re = "(->|\\.)[ \t]*under([^A-Za-z0-9_]|$)" else re = "\\.[ \t]*under([^A-Za-z0-9_]|$)" hit = (code ~ re) # Line-split continuation: operator at EOL, token opening the # next code line. Comment-only lines keep the pend alive. if (!hit && pending && code ~ /^[ \t]*under([^A-Za-z0-9_]|$)/) hit = 1 if (hit) { printf("%s:%d: raw under-token read outside the chase accessor; " \ "route via type_chase_named (C) / tichase (ww), or annotate " \ "peel-ok: \n", cur_file, FNR) nviol++ } if (code !~ /^[ \t]*$/) { if (is_c) pending = (code ~ /(->|\.)[ \t]*$/) else pending = (code ~ /\.[ \t]*$/) } } END { exit (nviol > 0 ? 1 : 0) } ' $files