Route the 16 routable bare-primsize GUARD sites (is-primitive / struct-vs-prim dispatch) through the #101 aliasprimsize SSoT helper. Byte-NEUTRAL by construction: an alias-narrow name is already neutralized downstream by the same arm, so routing emits no new asm (the empty-flip-set ken oracled). Shape-A exclude-prim-early (3): cgenutil sretretsize / structparamsize / structfloatclass — `primsize>0 return` then structlookup→nil returns the same value; route returns it early, same. Shape-B prim-guard-then-structlookup (13): cgenutil 4604/4650 + cgenexpr 4136/10244 + the 9-site CALL/assign cluster — primsize==0 →structlookup→nil→fall to normal; route skips the block→same normal. Install the peellint bare-primsize FINALE (B7 lint-fuse contract): tools/peellint now rejects any bare primsize() in the ww stage outside the annotated whitelist. Evasion-hardened per the B7 lesson — a character scan (comments + string/char literals stripped first) and a LEFT+RIGHT word-bounded match of the bare `primsize` TOKEN (not just `primsize(`), so the aliasprimsize() wrapper is never a hit and every compiling spelling reds: the call primsize(nm), the paren-wrap (primsize)(nm), the function-value bind `let p = primsize`, and any line-split. ww-only (the C stage dealiases via type_chase_named, no primsize symbol). Two independent exemption windows (peel-ok vs primsize-ok) so neither rule blinds the other. Runs as a make-test dep. Whitelist the 6 designed exemptions with primsize-ok WHY-annotations: machinery — aliasprimsize body (SSoT chase) | typenodeprimresolved + exprprimresolved (#11/#33 prim-resolver chasers) | cgcast leaf-loop + cgenexpr #11 deref-store (own ps==0 fallback; route would regress #11) | the primsize oracle/definition itself (nothing below to chase). structural — elemsizeof x2 + paramfieldsize (chase lives in the -c twin elemsizeofc; threading c is the dormant #110). Empty-flip-set proof: zero C bytes; cstage binaries bit-identical; bootstrap byte-id 990-997 + 950 all green (w6c == w6c_ww on the full selfhost, self-rebuild identical); combined.ww (w6c + wwdump) regen idempotent; sizelint 0; peellint 0 (raw-peel AND bare-primsize over the whole tree = the close-by-construction proof, zero unwhitelisted survivors). Tests: 944_peellint_gate +14 rows (bare / space-before-paren / name-at-EOL split / string-blind opener / paren-wrap / fn-value-bind RED; aliasprimsize wrapper + primsize-ok annotated GREEN; corrupt annotation RED; independent peel/primsize windows; C-file out-of-scope). Closes the #101 primsize-alias family by construction. #109.
196 lines
7.6 KiB
Bash
Executable File
196 lines
7.6 KiB
Bash
Executable File
#!/bin/sh
|
|
# tools/peellint — gate against two raw single-resolve shapes that
|
|
# bypass the alias-chase accessors.
|
|
#
|
|
# RULE 1 (raw TY_NAMED single-peel reads) — 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.
|
|
#
|
|
# RULE 2 (bare primsize() name-keyed width) — the #101/#109 close-by-
|
|
# construction contract. primsize(name) is the ww-stage primitive-width
|
|
# table; it is ALIAS-BLIND — a narrow alias (`type my32 = u32`) returns
|
|
# 0, defaulting strides/widths to 8 (the #101 struct-fill miscompile and
|
|
# the #109 is-primitive GUARD family). aliasprimsize(c, name) is the
|
|
# SSoT chase (primsize else aliaslookup-chase, cgenutil.ww). A bare
|
|
# primsize() outside the chase machinery is the forbidden shape: the
|
|
# size/guard sites route through aliasprimsize so an alias resolves.
|
|
# This rule is ww-only — the C side dealiases via type_chase_named and
|
|
# has no primsize symbol. ZERO bare primsize() calls may exist in the
|
|
# ww stage outside the annotated whitelist.
|
|
#
|
|
# Both rules land in the same commit that deletes the last raw shape and
|
|
# keep the class unwritable.
|
|
#
|
|
# What rule 1 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. Rule 2 has the symmetric edge: a size
|
|
# computed by a hardcoded literal instead of primsize is caught by
|
|
# sizelint (rule 13), not here.
|
|
#
|
|
# 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 tokens are matched accessor-spelling-wide:
|
|
# rule 1: `->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).
|
|
# rule 2 (ww only): the bare `primsize` token, LEFT+RIGHT word-bounded
|
|
# so the superstring `aliasprimsize` is NOT a hit. Matching the
|
|
# standalone token (not just `primsize(`) closes the call form
|
|
# `primsize(nm)`, the paren-wrap `(primsize)(nm)`, the function-value
|
|
# bind `let p = primsize`, and every line-split — all of which
|
|
# compile and reintroduce the alias-blind width (review probes).
|
|
# Right/left token bounds keep prose like "io.underread" (check.ww) and
|
|
# "aliasprimsize" out.
|
|
#
|
|
# Exemption: a line containing `peel-ok` (or the equivalent landed
|
|
# spelling `peellint-ok`) exempts rule-1 violations on itself and the
|
|
# following 9 lines; a line containing `primsize-ok` exempts rule-2
|
|
# violations over the same window. The windows are kept separate so a
|
|
# rule-1 annotation cannot blind a rule-2 bug and vice versa. Wide
|
|
# enough that one annotation atop a short construction/chase body covers
|
|
# it, narrow enough that a stray shape can't hide behind a distant
|
|
# annotation. Reasons stay WHY-only (rule 8): construction, chase body,
|
|
# recursive chase, resolve-state probe, structural-by-design sizer, 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)$/)
|
|
und_exempt_until = 0
|
|
prim_exempt_until = 0
|
|
in_block = 0
|
|
pending_und = 0
|
|
}
|
|
|
|
# Whitelist annotations: arm the exemption windows 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. The two windows are independent.
|
|
tolower($0) ~ /peel(lint)?-ok([^a-z0-9_]|$)/ {
|
|
if (FNR + 9 > und_exempt_until) und_exempt_until = FNR + 9
|
|
}
|
|
tolower($0) ~ /primsize-ok([^a-z0-9_]|$)/ {
|
|
if (FNR + 9 > prim_exempt_until) prim_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++
|
|
}
|
|
}
|
|
|
|
{
|
|
und_exempt = (FNR <= und_exempt_until)
|
|
prim_exempt = (FNR <= prim_exempt_until)
|
|
blank = (code ~ /^[ \t]*$/)
|
|
|
|
# RULE 1 — raw under-token peel. Both C spellings peel:
|
|
# p->under and (*p).under / v.under.
|
|
if (!und_exempt) {
|
|
if (is_c)
|
|
ure = "(->|\\.)[ \t]*under([^A-Za-z0-9_]|$)"
|
|
else
|
|
ure = "\\.[ \t]*under([^A-Za-z0-9_]|$)"
|
|
uhit = (code ~ ure)
|
|
# Line-split continuation: operator at EOL, token opening the
|
|
# next code line. Comment-only lines keep the pend alive.
|
|
if (!uhit && pending_und && code ~ /^[ \t]*under([^A-Za-z0-9_]|$)/)
|
|
uhit = 1
|
|
if (uhit) {
|
|
printf("%s:%d: raw under-token read outside the chase accessor; " \
|
|
"route via type_chase_named (C) / tichase (ww), or annotate " \
|
|
"peel-ok: <why>\n", cur_file, FNR)
|
|
nviol++
|
|
}
|
|
}
|
|
|
|
# RULE 2 — bare primsize token (ww only). LEFT+RIGHT word bounds
|
|
# so the superstring aliasprimsize() is never a hit and a longer
|
|
# identifier with a primsize prefix is not matched. Matching the
|
|
# token (not `primsize(`) catches the paren-wrap `(primsize)(nm)`,
|
|
# the function-value bind `let p = primsize`, and every line-split —
|
|
# all compile and reintroduce the alias-blind width (review probes).
|
|
if (!is_c && !prim_exempt) {
|
|
if (code ~ /(^|[^A-Za-z0-9_])primsize([^A-Za-z0-9_]|$)/) {
|
|
printf("%s:%d: bare primsize outside the chase accessor; " \
|
|
"route via aliasprimsize (ww), or annotate " \
|
|
"primsize-ok: <why>\n", cur_file, FNR)
|
|
nviol++
|
|
}
|
|
}
|
|
|
|
# Pending updates. An exempt line resets its pend (an annotated
|
|
# operator/name must not carry into a non-exempt next line); a blank
|
|
# or comment-only line keeps the pend alive; otherwise re-derive.
|
|
if (und_exempt) pending_und = 0
|
|
else if (!blank) {
|
|
if (is_c) pending_und = (code ~ /(->|\.)[ \t]*$/)
|
|
else pending_und = (code ~ /\.[ \t]*$/)
|
|
}
|
|
}
|
|
|
|
END { exit (nviol > 0 ? 1 : 0) }
|
|
' $files
|