diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index d9637529..8f965336 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -16,8 +16,20 @@ The first public pitch is **not** "we're building a borrow checker for C#". It i > leaks, missing `Dispose`, DI lifetime mismatch, and pooled-buffer misuse. That is concrete, painful, and shippable without a five-year R&D detour. The -borrow checker is the *first combat module*, not the whole universe. The -long-term identity the backlog is aiming at: +borrow checker is the *first combat module*, not the whole universe. + +Read structurally, Own.NET is **one resource/lifetime analyzer with profiles** — +subscriptions, timers, `IDisposable`, DI lifetimes, pooled buffers — and **WPF is +the first configured profile, not the identity.** The engine emits domain-neutral +core verdicts (`OWN001/002/003/014`) plus a `[resource: …]` kind tag; a profile only +contributes the lifetime facts (WPF's `ViewModel < Window < App`, `Loaded`/`Closed` += release regions). (A dedicated `[profile: …]` label is **not** emitted today — it +is a consolidation-backlog item.) +Code names that still read `WPFxxx` are pattern-catalog IDs, not emitted codes — the +catalog rename (`SUB`/`TMR`/`DISP`) and the other consolidation items are recorded in +[docs/notes/consolidation-and-positioning.md](notes/consolidation-and-positioning.md). + +The long-term identity the backlog is aiming at: > **An external static-contract layer for C#/.NET** that adds ownership, > typestate, effects, capabilities, and domain-specific types **without @@ -46,13 +58,26 @@ long-term identity the backlog is aiming at: ### What the C# frontend deliberately does NOT touch yet -`async`/`await`, full generics, LINQ, closures/lambdas, interprocedural analysis, -virtual dispatch, whole-program analysis, source generators, `unsafe` pointer -arithmetic, the XAML/binding engine. Not "never" — just not *before* the tool has -found its first real bug. An `async` method in v0 is honestly skipped (or flagged +`async`/`await`, full generics, LINQ, general closure/dataflow analysis, virtual +dispatch, whole-program analysis, source generators, `unsafe` pointer arithmetic, +the XAML/binding engine. Not "never" — just not *before* the tool has found its +first real bug. An `async` method in v0 is honestly skipped (or flagged "unsupported"), because honestly skipping beats confidently lying; the market for confident-but-wrong tooling is already saturated. +Two honest exceptions have since been carved out, because a real bug needed each: + +- **Syntactic lambda-event-handler classification.** General closure analysis stays + out, but the frontend *does* recognise an inline `evt += (s,a) => …` handler — an + inline handler caches no delegate, so it can never be `-=`'d, which is part of the + subscription-leak profile. It is the syntactic *shape*, not dataflow over captures. +- **Bounded, *modular* interprocedural — in the core, not the frontend.** The + frontend still extracts facts one method at a time. But the core now checks + ownership *across* methods **compositionally, against a callee's contract** + (`consume`/`borrow`, inferred from its body when unannotated) — the signature is + the cut point, never whole-program points-to (P-006/2b). "Interprocedural" in the + exclusion above means the intractable *whole-program* kind, which stays out. + ## Priorities Targets are ranked by four criteria: (1) the pain is frequent or expensive, diff --git a/docs/lifetimes.md b/docs/lifetimes.md index 4691a2b8..ae76a1df 100644 --- a/docs/lifetimes.md +++ b/docs/lifetimes.md @@ -124,6 +124,15 @@ weak-reference policy как явный escape-hatch. ## 5. Каталог кодов (OWN-WPF) и куда какой слайс +> **Соглашение об именах.** `WPFxxx` ниже — это **ID паттернов каталога** +> (таксономия болей), а **не** коды, которые эмитит движок. Движок всегда выдаёт +> доменно-нейтральные **core-коды** (`OWN001/002/003/014`) плюс **ярлык ресурса** +> (`[resource: subscription token]`); отдельного `[profile: …]`-ярлыка движок пока +> **НЕ** эмитит (это пункт consolidation-бэклога). То есть `event +=` — это +> `OWN001`/`OWN014` класса subscription, а не отдельный «WPF001»-эмиттер. +> Массовое переименование каталога (`WPFxxx → SUB/TMR/DISP`) отложено как +> low-value churn — см. `docs/notes/consolidation-and-positioning.md`. + | Код | Смысл | Сводится к | Слайс | |-----|-------|-----------|-------| | WPF004 | `Subscribe` вернул owned-токен, результат проигнорирован → утечёт | `OWN001` | **#1** | diff --git a/docs/notes/consolidation-and-positioning.md b/docs/notes/consolidation-and-positioning.md new file mode 100644 index 00000000..6c4b0827 --- /dev/null +++ b/docs/notes/consolidation-and-positioning.md @@ -0,0 +1,95 @@ +# Consolidation & positioning backlog + +Working notes from an external architecture review (read-only pass over the repo: +README, ROADMAP, P-001/P-004/P-005/P-006, the OwnIR bridge and the Roslyn +extractor). The review's verdict and the cheap fixes are recorded here so the +deferred items do not evaporate; the cheap fixes were applied directly (see +below), the rest are **explicitly deferred** with rationale. + +## Verdict (agreed) + +The direction is right: **one core checker, Roslyn extracts facts only, OwnIR is +the seam, P0 is bug-driven (events / timers / `IDisposable` / DI / pool), and +lifetime regions reached `OWN014` through real C# facts.** The skeleton is sound. +The standing advice — **"strengthen the form, don't expand the dream"** — is taken. + +One honest caveat about the review itself: it was a *read-only* pass (tests not +run), so it is weighted toward what you see **reading** (naming, file size, +schema) and blind to what you only see **running** (does Own.NET catch real bugs +others miss?). What is still unproven is **value**, not **form** — no rename or +file-split adds a single caught bug. So the highest-leverage next move is *not* on +this list: it is running the differentiation oracle on more real repos. + +## Done now (this PR — cheap, doc-only, ~zero risk) + +- **Doc drift fixed.** ROADMAP "frontend does NOT touch closures/interprocedural" + now carves out the two honest exceptions that already exist: syntactic + lambda-event-handler classification, and bounded *modular* interprocedural + (contracts/inference) **in the core, not the frontend**. +- **Positioning sharpened.** ROADMAP framing now states plainly: Own.NET is one + resource/lifetime analyzer **with profiles**, WPF is the *first* profile, the + engine emits core `OWNxxx` codes + a `[resource: …]` kind tag (a dedicated + `[profile: …]` label is itself one of the deferred items below). +- **Naming convention noted** in `docs/lifetimes.md`: `WPFxxx` are pattern-catalog + IDs, not emitted codes. + +## Deferred (recorded, NOT scheduled) + +Ordered by the review; annotated with the real cost/benefit and the trigger that +should pull each off the shelf. + +### 1. OwnIR v1 schema — rename `subscriptions` → `resources` (+ `captures`) +- **Why.** The component-level field is historically named `subscriptions` but + holds any owned-resource record (subscription / timer / disposable / capture). +- **Reality check.** Milder than the review implies: `services` and `functions` + are **already** separate top-level fields; only the component resource list is + misnamed, and a `resource` kind discriminator already makes it work. +- **Cost.** Breaking `v0 → v1`: bridge + every fixture + the C# extractor + tests. + Pure rename churn, **zero new caught bugs.** +- **Decision.** Do NOT do as a standalone churn PR. **Fold into the next PR that + already touches the schema** (e.g. when a new resource kind needs first-class + fields), with a transition window where the bridge reads both names. + +### 2. Split the Roslyn extractor (`Program.cs`) +- **Why.** `OwnSharp.Extractor/Program.cs` carries input discovery, event/timer/ + disposable extraction, self-owned/handler/source-lifetime classifiers, lambda + detection and IDisposable flow lowering in one file. +- **Cost.** Behaviour-preserving C# refactor; **but it is the frontend, which is + CI-validated only (no local dotnet here), so it is the riskier kind to refactor + blind, and it adds no caught bug.** +- **Decision.** Defer until it actually causes merge pain. Target split: + `InputDiscovery` / `CompilationBuilder` / `OwnIrWriter`, `Extractors/*`, + `Analysis/*`; first cut = peel `EventSubscriptionExtractor` + + `SourceLifetimeClassifier` + `HandlerClassifier` out, keeping the + `IDisposable` local-flow lowering separate from the subscription extractor. + +### 3. Catalog rename `WPFxxx → SUB/TMR/DISP` +- **Why.** Positioning — `event +=` is `SUB001`, not `WPF001`. +- **Reality check.** Emitted codes are **already** core `OWN` + labels; `WPFxxx` + live only in the docs/catalog. So this is doc churn, not an emitter change. +- **Decision.** Do it together with the OwnIR v1 / profile-config work, not alone. + Keep genuinely WPF/XAML-specific patterns (ResourceDictionary / DataContext / + Binding / WeakEventManager / visual-tree ownership) under a `WPF` profile label. + +## Genuinely next *capabilities* (features, not refactors) + +The review filed these under "next" but they are new capability, tracked where +features belong: + +- **Transitive contract inference** — resolve the one ambiguous inference case + (a param only *forwarded* to another call) from the callee's contract, fixpoint + over the island's call-graph SCC. This is the real next brick: it would make the + `forward → sink(consume)` shape catch the caller's double-dispose that v1 + deliberately leaves silent. (Follows P-006/2b.) +- **Lambda-handler tier** — finer subscription diagnostics for inline handlers + (no unsubscribe handle; captures `this`/local state), tiered by source lifetime + exactly like the existing static→`OWN014` / injected→`OWN001`-warning split. +- **DI registration extractor** — the C# frontend for `AddSingleton/Scoped/ + Transient` + constructor injection that feeds the already-built DI001 core + (continues the lifetime story; sells to ASP.NET). + +## The actual priority + +Prove value, don't reshape form: run the oracle on more real OSS C# repos and +widen the differentiation set (where Own.NET catches what CodeQL / Infer# miss). +See `docs/notes/oracle.md` and `docs/notes/real-world-mining.md`.