The last four raw `->under` reads outside the whitelist were the
static-DATA emitters' ELEMENT-type single peels (the outer type already
chased): emit_array_lit_bytes:14356, emit_strarray_data:14574,
emit_slice_data:14788, let_pre_intern:15088 -> type_chase_named.
:15088 is the :14574 row's label-order leg and must flip in the same
commit or _S_ labels intern in emit order, not decl order (the in-tree
comment at the site); the strarr row's byte-id is the coupling proof.
Behavior moves (ken B7 first-position oracle + impl pre-state, all
pre-observed at 05f7af7):
- [N]alias-struct + [N]alias-str globals graduate cs link-ERR
("undefined reference") -> 0/0 BYTE-ID (cs emits ww's DATAW).
- zero-consumer latent silence closed: a never-referenced
2-level-elem-alias global silently lacked DATA (no reference, no
link error); now emits, pinned by the byte-id cell.
- []alias-str diagnostic routing: the alias escaped the 3-way
slice-of-{str,slice,tagged} fatal onto the downstream "not a
foldable constant" text — now the intended 3-way text (== control).
- []alias-tagged DESIGNED NARROWING: the alias dodged the 3-way fatal
ENTIRELY — cs silently accepted + RAN WRONG for reachable consumer
shapes (review-verified at base: a len+payload-read probe exits 1;
the len-only row was luck-correct). Now loud with the 3-way text;
widen what the gate SEES, never what it ACCEPTS (B6-c2 precedent).
- kb7_slc/slc0 scalar legs byte-NEUTRAL (the synthesized-array
choke-point already handled them); full kb corpus sweep: movers are
exactly the two graduation shapes, nothing else.
tools/peellint (sizelint clone, dep of test/test-unit): character-scan
strips comments and string/char literals, then matches the under-token
accessor-spelling-wide — `->under`/`.under` in C (deref-dot is the
same peel), `.under` in ww, optional whitespace after the operator,
and the line-split continuation (operator at EOL, `under` next line).
Scope cmd/wcc + cmd/w6c + selfhost/cmd/wcc + lib/ww (lib/ww/typ.ww
ruled IN — it is type.c's ww mirror, the accessor layer itself);
`peel-ok`/`peellint-ok` annotations exempt a 10-line window. Green at
this tip = zero unwhitelisted raw peels survive; the gate lands in the
commit that deletes the last raw read (the-funnel-completing-commit-
carries-the-gate; sizelint precedent). Whitelist, 27 entries:
cmd/wcc/type.c :78 :141 construction, :162 chase body,
:180 :193 :214 recursive chase
cmd/wcc/check.c :102 :2572 resolve-state probes, :2586 construction
cmd/w6c/cgen.c :731 probe-cleared scan peel (B5-c1),
:813/:814 :834/:835 peel-ok #218 variant-match
lib/ww/typ.ww :316 construction, :374 :385 :410 :437 :447 :463
:475 :488 :514 recursive chase
selfhost/cmd/wcc/cgenutil.ww :1302 chase body (tichase),
:2759 probe-cleared peel
selfhost/cmd/wcc/check.ww :1815 construction (peellint-ok)
Negative validation wired into 944_peellint_gate (B4 precedent):
re-introduced raw peel (C and ww spellings) REDS the lint; corrupted
annotation (peel-okk-…, token-bounded matcher) REDS the lint; the
check.ww:3683 "io.underread" prose, a code read of a longer field, and
comment-quoted tokens are pinned green regression rows; real tree must
lint clean. 944_alias_emit_b7_run pins all four emit paths
table-driven (14 rows / 36 checks) incl. ken's ww observation cells
(ww checker rejects slice-literal globals, "let: not assignable" —
unmoved; plain []str louds at ww's own emitslicedata 3-way, pinned by
the shared needle).
REVIEW AMENDMENT (reviewer-B7, fix-what-you-find): the frozen tip's
regex matcher passed five compiling evasion spellings green — `t ->
under` spacing, `t->`/EOL + `under` next-line (both stages; ww parses
`t.`/EOL too), C deref-dot `(*t).under`, ww `t. under`, and a string
literal containing a block-comment opener that blinded the regex
comment-strip for the rest of the file. The matcher is now a
character scan (comments + string/char literals stripped before
matching) with the widened token rule above; all six spellings are
pinned RED rows in 944_peellint_gate (checks 10 -> 16). The 10-line
annotation window stays as designed (a peel within an annotation's
window is exempt by construction — the window IS the exemption
mechanism). Lint + test bytes only; zero compiler-source bytes moved
in review.
What this does NOT close, said out loud (f2-ruling): a consumer that
never spells `under` at all — a switch on t->kind that simply never
peels — has no token for the lint to see. The accessor+lint closes the
WRONG-PEEL class (single-peel where chase was needed) by construction;
the NO-PEEL class is closed only at sites where classification routes
through the internalized chasing helpers, and contained elsewhere by
the acceptance-commit-carries-tripwires doctrine, which stays standing
for every future acceptance widening. The gate does not make alias
bugs impossible; it makes the four-times-burned shape unwritable.
Rule-11 note: forced fuse — the four conversions ARE the last raw-read
deletions; peellint cannot be green one commit earlier (consumer-graph
-forces-the-fuse precedent, #61).
Invariants: cs asm byte-NEUTRAL on the whole bootstrap corpus (five
mains + smoke, base-input pre==post); five mains cs==ww byte-id at
tip; _ww binary quartet bit-identical to the W2 baseline (ww changes
are comment-only annotation bytes — codegen-inert, proven by the md5
hold); w6c_ww+wwdump main.combined.ww regen'd via make, idempotent;
989 lib ratchet zero flips (31 byte-id / 9 pinned-divergent / 3
pinned-wwreject across 43 units); sizelint 0; peellint 0;
make test-unit "all 294 tests passed" (292 + the two new suites).
203 lines
6.7 KiB
C
203 lines
6.7 KiB
C
/*
|
|
* 944_peellint_gate — teeth for tools/peellint (#5 alias-arc B7).
|
|
*
|
|
* The lint is the enforcement half of the close-by-construction
|
|
* contract: zero raw under-token reads in scope outside the annotated
|
|
* whitelist. A gate without negative validation can rot green (B4
|
|
* precedent), so this test pins BOTH directions:
|
|
*
|
|
* 1. real tree at HEAD lints CLEAN (the closure proof itself);
|
|
* 2. a re-introduced raw peel REDS the lint — C `->under` ternary
|
|
* and ww `.under` if-peel, the four-times-burned spellings;
|
|
* 3. a corrupted whitelist annotation REDS the lint (token-bounded
|
|
* `peel-ok` matcher: `peel-okk-…` must NOT exempt);
|
|
* 4. regression rows that must stay GREEN: the check.ww:3683
|
|
* "io.underread" prose (token bound), a code read of a longer
|
|
* field (`s.underread`), comment-quoted `.under`/`->under`
|
|
* prose (comment strip), and the already-landed `peellint-ok`
|
|
* sibling spelling (history is not re-spelled);
|
|
* 5. review-found evasion spellings REDS (B7 review probes E1-E6,
|
|
* every one compiles): `t -> under` spacing, `t->`/EOL +
|
|
* `under` next line (both stages' split), C deref-dot
|
|
* `(*t).under`, ww `t. under`, and a string literal containing
|
|
* a block-comment OPENER token that blinded the old regex
|
|
* comment-strip for the rest of the file.
|
|
*
|
|
* Scratch trees live under /tmp and exercise the lint via its ROOT
|
|
* override (sizelint-style), so the real tree is never touched.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
|
|
static char root[1024]; /* repo root (cwd when run via test/run) */
|
|
|
|
static int
|
|
runwait(const char *cmd)
|
|
{
|
|
int rc = system(cmd);
|
|
if (rc == -1) return -1;
|
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
write_file(const char *path, const char *body)
|
|
{
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) return -1;
|
|
fputs(body, f);
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
/* lint_scratch — run tools/peellint over a one-file scratch tree and
|
|
* return its exit code. relpath selects the in-scope directory. */
|
|
static int
|
|
lint_scratch(const char *scratch, const char *relpath, const char *body)
|
|
{
|
|
char cmd[2048], path[1400];
|
|
snprintf(cmd, sizeof cmd, "rm -rf %s", scratch);
|
|
runwait(cmd);
|
|
snprintf(path, sizeof path, "%s/%s", scratch, relpath);
|
|
char dir[1400];
|
|
snprintf(dir, sizeof dir, "%s", path);
|
|
char *slash = strrchr(dir, '/');
|
|
if (slash) *slash = '\0';
|
|
snprintf(cmd, sizeof cmd, "mkdir -p %s", dir);
|
|
if (runwait(cmd) != 0) return -1;
|
|
if (write_file(path, body) != 0) return -1;
|
|
snprintf(cmd, sizeof cmd,
|
|
"ROOT=%s sh %s/tools/peellint >/dev/null 2>&1", scratch, root);
|
|
int rc = runwait(cmd);
|
|
snprintf(cmd, sizeof cmd, "rm -rf %s", scratch);
|
|
runwait(cmd);
|
|
return rc;
|
|
}
|
|
|
|
struct lintrow { const char *label; const char *relpath;
|
|
const char *body; int wantexit; };
|
|
|
|
static const struct lintrow lintrows[] = {
|
|
{ "reinject_c_peel", "cmd/w6c/x.c",
|
|
"static Type *f(Type *t) {\n"
|
|
"\tType *u = (t->kind == TY_NAMED) ? t->under : t;\n"
|
|
"\treturn u;\n"
|
|
"}\n", 1 },
|
|
{ "reinject_ww_peel", "selfhost/cmd/wcc/x.ww",
|
|
"fn f(t: *tinfo) *tinfo = {\n"
|
|
"\tif (t.kind == tykind.TY_NAMED) { return t.under; };\n"
|
|
"\treturn t;\n"
|
|
"};\n", 1 },
|
|
{ "annotated_c_peel_ok", "cmd/w6c/x.c",
|
|
"static Type *f(Type *t) {\n"
|
|
"\tType *u = (t->kind == TY_NAMED) ? t->under : t; "
|
|
"/* peel-ok: probe */\n"
|
|
"\treturn u;\n"
|
|
"}\n", 0 },
|
|
{ "corrupt_annotation", "cmd/w6c/x.c",
|
|
"static Type *f(Type *t) {\n"
|
|
"\tType *u = (t->kind == TY_NAMED) ? t->under : t; "
|
|
"/* peel-okk-corrupt: probe */\n"
|
|
"\treturn u;\n"
|
|
"}\n", 1 },
|
|
{ "peellint_ok_spelling", "selfhost/cmd/wcc/x.ww",
|
|
"fn f(t: *tinfo, u: *tinfo) void = {\n"
|
|
"\t// peellint-ok: construction\n"
|
|
"\tt.under = u;\n"
|
|
"};\n", 0 },
|
|
/* check.ww:3683 regression: prose token "io.underread" must not
|
|
* trip the ww matcher (token bound), nor `.under` quoted in a
|
|
* line comment (comment strip). */
|
|
{ "io_underread_prose", "selfhost/cmd/wcc/x.ww",
|
|
"fn f(x: int) int = {\n"
|
|
"\t// #199 repro io.underread -> (size|io.eof|io.error)\n"
|
|
"\t// the NAMED.under chain stays terminating\n"
|
|
"\tlet v: int = x + 2; // io.underread again\n"
|
|
"\treturn v;\n"
|
|
"};\n", 0 },
|
|
{ "code_longer_field", "selfhost/cmd/wcc/x.ww",
|
|
"fn f(s: stream) int = { return s.underread; };\n", 0 },
|
|
{ "c_block_comment_prose", "cmd/wcc/x.c",
|
|
"/* walk the chain: a raw t->under read here\n"
|
|
" * would single-peel; t->under in prose only. */\n"
|
|
"int g(int x) { return x; }\n", 0 },
|
|
{ "lib_ww_in_scope", "lib/ww/x.ww",
|
|
"fn f(t: *tinfo) *tinfo = {\n"
|
|
"\tif (t.kind == tykind.TY_NAMED) { return t.under; };\n"
|
|
"\treturn t;\n"
|
|
"};\n", 1 },
|
|
/* Review-found evasions (all compile; pre-amendment lint passed
|
|
* every one of them green): the matcher must red each. */
|
|
{ "evade_c_spacing", "cmd/w6c/x.c",
|
|
"static Type *f(Type *t) {\n"
|
|
"\treturn (t->kind == TY_NAMED) ? t -> under : t;\n"
|
|
"}\n", 1 },
|
|
{ "evade_c_linesplit", "cmd/w6c/x.c",
|
|
"static Type *f(Type *t) {\n"
|
|
"\treturn (t->kind == TY_NAMED) ? t->\n"
|
|
"\t under : t;\n"
|
|
"}\n", 1 },
|
|
{ "evade_c_derefdot", "cmd/w6c/x.c",
|
|
"static Type *f(Type *t) {\n"
|
|
"\treturn (t->kind == TY_NAMED) ? (*t).under : t;\n"
|
|
"}\n", 1 },
|
|
{ "evade_ww_dotspace", "selfhost/cmd/wcc/x.ww",
|
|
"fn f(t: *tinfo) *tinfo = {\n"
|
|
"\tif (t.kind == tykind.TY_NAMED) { return t. under; };\n"
|
|
"\treturn t;\n"
|
|
"};\n", 1 },
|
|
{ "evade_ww_linesplit", "selfhost/cmd/wcc/x.ww",
|
|
"fn f(t: *tinfo) *tinfo = {\n"
|
|
"\tlet u: *tinfo = t.\n"
|
|
"\t\tunder;\n"
|
|
"\treturn u;\n"
|
|
"};\n", 1 },
|
|
{ "evade_c_string_blind", "cmd/w6c/x.c",
|
|
"static const char *s = \"/*\";\n"
|
|
"static Type *f(Type *t) { return t->under; }\n", 1 },
|
|
};
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
if (getcwd(root, sizeof root) == NULL) return 1;
|
|
|
|
int total = 0, fail = 0;
|
|
char cmd[2048], scratch[256];
|
|
|
|
/* 1. The closure proof: the real tree lints clean at HEAD. */
|
|
total++;
|
|
snprintf(cmd, sizeof cmd, "sh %s/tools/peellint", root);
|
|
if (runwait(cmd) != 0) {
|
|
fprintf(stderr, "peellint_gate: real tree NOT clean\n");
|
|
fail++;
|
|
}
|
|
|
|
/* 2-4. Scratch rows: negative validation + matcher regressions. */
|
|
int n = (int)(sizeof lintrows / sizeof lintrows[0]);
|
|
for (int i = 0; i < n; i++) {
|
|
total++;
|
|
snprintf(scratch, sizeof scratch, "/tmp/plint_%d_%d",
|
|
getpid(), i);
|
|
int got = lint_scratch(scratch, lintrows[i].relpath,
|
|
lintrows[i].body);
|
|
if (got != lintrows[i].wantexit) {
|
|
fprintf(stderr, "row[%s]: lint exit %d, want %d\n",
|
|
lintrows[i].label, got, lintrows[i].wantexit);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "peellint_gate: %d/%d checks failed\n",
|
|
fail, total);
|
|
return 1;
|
|
}
|
|
printf("peellint_gate: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|