Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
/target
**/*.rs.bk
*.pdb

# W7.3 fuzz workspace — build artifacts and generated modules stay local
tests/fuzz/grammar_v2/target/
tests/fuzz/grammar_v2/Cargo.lock
tests/fuzz/grammar_v2/out/

88 changes: 88 additions & 0 deletions docs/W7_3_FUZZ_BASELINE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# W7.3 E1+E2 baseline — parse-invariance run

Status: **BASELINE LANDED** (2026-07-05).
Branch: `w7/testing/fuzz-baseline` (PR #46).
Provenance: `tests/fuzz/grammar_v2/{Cargo.toml,src/gen.rs,roundtrip.py}` @ this commit.

## Setup

- **Generator**: `tests/fuzz/grammar_v2/src/gen.rs` (E1, W7.3-E1 commit + `max_stmts_per_fn` reconciliation).
- **Harness**: `tests/fuzz/grammar_v2/roundtrip.py` (E2).
- **t27c**: `/home/user/workspace/t27/target/release/t27c` (from t27 workspace, master post-#1348 era).
- **Seed**: `0xC0FFEE` base + per-module offset `+i`.
- **Corpus**: N=1000 modules generated to `/tmp/w73_baseline_1000/*.t27` (spec bodies not committed — reproducible from seed).

## Invariants tested

For each generated module, the harness runs three passes:

1. **Parse-success**: `t27c parse <path>` returns exit code 0 (no error, no panic).
2. **Determinism**: parsing the same input twice yields identical AST (after normalization).
3. **Whitespace-invariance**: three non-semantic mutations are applied and re-parsed:
- `extra_spaces` — double every leading-indent space run.
- `extra_newlines` — add blank line after every `}\n`.
- `trailing_ws` — add trailing spaces to every non-empty line.
Each mutated variant must parse to the same normalized AST as the original.

## Normalization

The harness strips `line: N,` fields from `t27c parse`'s Debug-formatted AST before comparison, because these are source-position metadata derived from layout, not structural content. Extra newlines shift them without changing meaning. Any remaining structural change after stripping is treated as a real invariance violation.

## Results (N=1000, seed range `0xC0FFEE..0xC0FFEE+999`)

| Metric | Value |
|---|---|
| Parse-success | **1000 / 1000 (100.0%)** |
| Determinism | **1000 / 1000 (100.0%)** |
| Whitespace-invariance | **1000 / 1000 (100.0%)** |
| Parse errors | 0 |
| Panics | 0 |
| Non-determinism | 0 |
| Elapsed | 9.9 sec (3000+ subprocess calls: baseline + determinism + 3 mutations per input) |

All three success criteria from `W7_3_FUZZ_BASELINE_PLAN.md` §Success-criterion met:

- ✓ 100% grammar-valid generations parse cleanly (target: 100%).
- ✓ 100% whitespace-invariant (target: ≥95%).
- ✓ 0 panics in t27c parser.

## Interpretation

**What this claim IS**: for the grammar subset E1 currently covers (Module, FnDecl with zero params, Let / Return stmts, Expr = Literal / Ident / BinOp / Cast / Cmp, six primitive types), t27c parses cleanly, deterministically, and is invariant to non-semantic whitespace changes across 1000 random seeds.

**What this claim IS NOT**:
- Not a differential test — this is parser self-consistency only. Real backend-differential (E3) needs the upstream Stmt::Let fix from [t27#1401](https://github.com/gHashTag/t27/issues/1401) to land first.
- Not full-grammar coverage. Missing from E1 today: function parameters, `Call`, `Index`, `If` statements, `UseDecl`, `ConstDecl`. See "Tracked TODOs before E3" below.
- Not a full round-trip via pretty-printer. t27c doesn't expose a public pretty-printer in the current version. Parse-invariance is a strict subset of the intended round-trip and still catches parser non-determinism, whitespace-sensitivity, and panics. Full round-trip via pretty-printer is a TODO once t27c exposes one.

## Discipline honesty

One methodological wrinkle was caught during the smoke run (N=20 before N=1000):

The first version of `normalize_ast` collapsed whitespace but left `line: N` fields intact. This produced a 75% failure rate on the `extra_newlines` mutation, because adding blank lines shifts line numbers for every subsequent AST node. The failure was NOT a parser bug — it was over-strict normalization treating source-position metadata as structural content. Fix: strip `line: N,` fields before comparison. After the fix, N=20 smoke passed 100%, and the N=1000 full run followed. This wrinkle is documented so the normalization choice is auditable and the "100% invariance" claim is understood as "invariant modulo source-position metadata," not "byte-for-byte identical output."

## Tracked TODOs before E3 unblock

The E1 grammar subset does not exercise function parameters. The W6.2 audit found the `Vec<>` defect (E0107, Class 2) lives in param-position. E3's differential power depends on exercising the grammar regions where bugs hide. Therefore:

- **Before E3**: extend E1 to generate function parameters (including collection params like `Vec<u8>`) and `Call` / `Index` expressions.
- **Backstop timer for E3**: 2026-07-19 12:24 UTC (14 days from t27#1401 publication), or terminal event on t27#1401 (won't-fix / closing PR / explicit reject), whichever comes first. See `W7_COLLAB_OPTIONS.md` §external-dep-timer rule.

Between now and that deadline, E1 grammar expansion is the primary open workstream on this branch.

## Reproducibility

```bash
# From tri-net workspace root.
cd tests/fuzz/grammar_v2
cargo build --release
W73_OUT=/tmp/w73_baseline_1000 ./target/release/gen 1000 0xC0FFEE
cd ../../..
python3 tests/fuzz/grammar_v2/roundtrip.py /tmp/w73_baseline_1000 --out /tmp/w73_baseline_1000_report.json
```

Expected: `ok=1000 parse_err=0 mut_fail=0 non_det=0`, elapsed <15 sec on a modern x86_64 sandbox.

## Anchor

phi^2 + phi^-2 = 3
93 changes: 93 additions & 0 deletions docs/W7_3_FUZZ_BASELINE_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# W7.3 — Grammar-directed fuzz baseline

Status: **PLAN** (2026-07-05) — awaiting first generator commit.
Branch: `w7/testing/fuzz-baseline`.
Parent: W6.1 lexical fuzzer (`tests/fuzz/` — token-level, tautological на shared front-end).

## Задача

Заменить lexical fuzzer (W6.1, weak-point 1.5 из `W6_WEAK_POINTS_AND_W7_PLAN.md`) на **grammar-directed generator** в стиле YARPGen. W6.1 генерировал token streams и проверял, что parser не panic'ает — 100% agreement был тавтологией, потому что все три backend'а (Rust / C / Zig) шарят один front-end. Реальная differential мощь возможна только после того, как:

1. Генератор эмитит **валидные по grammar** программы (не token noise).
2. Round-trip harness проверяет структурную инвариантность parser'а.
3. Backend-differential применяется на well-typed inputs, где расхождение = семантический bug, а не parse-noise.

## Scope W7.3

Три этапа:

### E1 — Grammar-directed generator (первый коммит)

- `tests/fuzz/grammar_v2/generator.rs` — production rules с weighting.
- Grammar покрытие minimum:
- `Module { UseDecl* ConstDecl* FnDecl+ }`
- `FnDecl { name, params, ret_type, body }`
- `Stmt ::= Let | Return | If | ExprStmt`
- `Expr ::= Literal | Ident | BinOp | Cast | Call | Index`
- Types: `u8 | u16 | u32 | u64 | usize | bool`
- Depth-bounded generation: max depth 6, max stmt count per fn 20.
- Seed-reproducible через `StdRng::seed_from_u64`.
- Output: N=1000 генераций в `target/fuzz/w7_3/*.t27`.

### E2 — Parse-invariance harness (второй коммит, **LANDED**)

Оригинальный план требовал full round-trip через pretty-printer, но t27c в current release не экспозит public pretty-printer. Parse-invariance — strict subset intended round-trip и ловит тот же класс багов (parser non-determinism, whitespace-sensitivity, panics):

- `tests/fuzz/grammar_v2/roundtrip.py`:
1. Gen spec → `t27c parse` → baseline AST (Debug-format).
2. Determinism: parse тот же input второй раз → identical normalized AST.
3. Whitespace-invariance: 3 non-semantic мутации (extra_spaces / extra_newlines / trailing_ws) → parse → identical normalized AST.
- Normalization strips `line: N,` метаданные (source-position, не structural). Остальное collapse whitespace.
- Metric: `parse_ok_rate`, `invariance_ok_rate`, failure classes (parse_error / non_determinism / mutation_changed_ast:<mode>).
- Full round-trip через pretty-printer — TODO когда t27c экспозит pretty-printer.

**Baseline result**: N=1000, 100.0% parse_ok / 100.0% invariance_ok / 0 panics / 9.9 sec. См. `docs/W7_3_FUZZ_BASELINE.md`.

### E3 — Backend differential (третий коммит, зависит от W7.1 upstream fix)

- Same input → `t27c gen rust|c|zig` → compile → runtime output.
- Differential trigger: any two backends диверджируют на same seed.
- **Caveat**: E3 валиден только когда upstream Stmt::Let fix у t27c приземлится. До этого gen/rust не имеет `let`, дифференциал структурно infeasible (см. W6.2 audit §3-5).
- Пока E3 blocked, E1+E2 работают независимо на current tree.

## Baseline run

Первый full run после E1+E2:
- N=1000 генераций.
- Distribution по (depth × stmt-count) — гистограмма в `docs/W7_3_FUZZ_BASELINE.md` (post-E2 doc, отдельный PR).
- Grammar coverage: доля production rules, exercised хотя бы одной генерацией.

## Success criterion

E1+E2 baseline считается **зелёным**, если:
- 100% валидных по grammar генераций parse'ятся без ошибок.
- ≥95% round-trip'ов структурно equal (allowed slack — pretty-printer whitespace normalization).
- 0 panics в t27c parser.

Любое отклонение — bug в parser или в pretty-printer, файлится как t27c issue (не tri-net) с seed'ом воспроизведения.

## Caveats и honest scope

- **Codegen-only vs parser-side ambiguity**: baseline валиден пока upstream Stmt::Let fix остаётся codegen-only. Если maintainer t27 определит проблему как parser-side (маловероятно — spec содержит `let`, значит parser их видит), baseline придётся пересобрать: parser может уже сейчас терять information, которую мы предположительно проверяем round-trip'ом.
- **Not a differential test** до E3. E1+E2 только проверяют parser self-consistency. Реальная differential мощь — E3, blocked на upstream.
- **Grammar в этом плане — approximate**. Ground truth grammar сидит в `t27c/src/parser.rs` upstream. Первый E1 коммит перекроет subset, но не 100% grammar; расширение — итеративно.

## Tracked TODOs before E3 unblock

GLM-5.2 peer-review PR #46 @ 6c0c93d выявил coverage-gap: текущий E1 эмитит zero-param functions. Но W6.2 audit нашёл Vec<>-defect (E0107 Class 2) в **param-position** — E3 backend-differential будет слепым к этому дефекту, если grammar не расширить. Backstop таймер t27#1401 = 2026-07-19 12:24 UTC (14 дней). За это окно:

- [ ] Extend `gen_fn` to emit **function parameters** (от 0 до 4, mixed primitives + хотя бы один collection type).
- [ ] Add `Call` expression to `gen_expr` с recursion на другие генерируемые функции.
- [ ] Add `Index` expression если grammar поддерживает (верифицировать через `parse` на minimal specs).
- [ ] Add `If` statement branching (grammar уже в plan’e, но в code нет).
- [ ] Reduce dead-let частоту (вес ident-branch в `gen_expr` → 40%+).

Статус обновлять в этом файле по мере выполнения.

## Отношение к W6.1

W6.1 lexical fuzzer НЕ deprecated — token-level fuzzing ловит другой класс bugs (parser panic на malformed input). W7.3 grammar-directed — комплементарен, не замена. Оба живут в `tests/fuzz/`, разными namespace'ами.

## Anchor

phi^2 + phi^-2 = 3
18 changes: 18 additions & 0 deletions tests/fuzz/grammar_v2/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "w7_3_grammar_fuzz"
version = "0.1.0"
edition = "2021"
license = "Apache-2.0"
publish = false

[dependencies]
rand = "0.8"
rand_chacha = "0.3"

[[bin]]
name = "gen"
path = "src/gen.rs"

[profile.release]
opt-level = 3
lto = false
Loading
Loading