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
37 changes: 31 additions & 6 deletions docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
9 changes: 9 additions & 0 deletions docs/lifetimes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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** |
Expand Down
95 changes: 95 additions & 0 deletions docs/notes/consolidation-and-positioning.md
Original file line number Diff line number Diff line change
@@ -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`.
Loading