-
Notifications
You must be signed in to change notification settings - Fork 0
Quality gate, lifetimes module (WPF leaks), spec + proposals, and a real C# leak pipeline #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
4f6716f
Add ruff + mypy --strict quality gate with exhaustive node dispatch
claude 52fd4c9
Freeze AST node dataclasses (immutable after parse)
claude 537909e
docs: design for the lifetimes module (WPF leak / lifetime checker)
claude e95e631
lifetimes slice #1: WPF leak corpus via resource-kind metadata
claude 4520868
lifetimes slice #2: lifetime regions + region-escape theorem (OWN014)
claude 9b1e9d1
Add normative spec/ + forward-looking docs/proposals/ + conformance p…
claude 69a536a
spec: complete the coverage map (Grammar, CLI, policy blocks); broade…
claude 375fa70
P-001 v0: C# -> OwnIR -> core leak pipeline (event += without -=)
claude 26b868a
Address CodeRabbit review (9 of 11 findings)
claude ef5b9ff
ast_nodes: document that frozen=True is shallow by design (CodeRabbit…
claude File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // FIXED. The callback guards on the disposed flag (and/or the subscription is | ||
| // disposed only after the dispatcher queue is drained), so nothing touches the | ||
| // subscription-backed state after Dispose(). | ||
| public sealed class CustomerViewModel : IDisposable | ||
| { | ||
| private readonly IDisposable _sub; | ||
| private bool _disposed; | ||
|
|
||
| public CustomerViewModel(IEventBus bus) | ||
| { | ||
| _sub = bus.Subscribe<CustomerChanged>(OnCustomerChanged); | ||
| } | ||
|
|
||
| private void OnCustomerChanged(CustomerChanged e) | ||
| { | ||
| if (_disposed) return; // do not touch disposed state | ||
| Refresh(); | ||
| } | ||
|
|
||
| private void Refresh() { /* ... */ } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| _disposed = true; | ||
| _sub.Dispose(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // BUGGY (representative WPF pattern, hand-reduced into case.own). | ||
| // | ||
| // The VM disposes its subscription on close, but a callback that was already | ||
| // queued on the dispatcher still runs and touches the (now disposed) state. In | ||
| // real code this surfaces as an ObjectDisposedException or a read of torn state. | ||
| public sealed class CustomerViewModel : IDisposable | ||
| { | ||
| private readonly IDisposable _sub; | ||
| private bool _disposed; | ||
|
|
||
| public CustomerViewModel(IEventBus bus) | ||
| { | ||
| _sub = bus.Subscribe<CustomerChanged>(OnCustomerChanged); | ||
| } | ||
|
|
||
| private void OnCustomerChanged(CustomerChanged e) | ||
| { | ||
| // a late, already-dispatched callback: runs after Dispose() | ||
| Refresh(); // touches subscription-backed state after it was disposed | ||
| } | ||
|
|
||
| private void Refresh() { /* reads disposed state */ } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| _disposed = true; | ||
| _sub.Dispose(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| module WpfHandlerAfterDispose | ||
|
|
||
| // Same subscription-token protocol, tagged with its kind. | ||
| resource Subscription { | ||
| acquire Subscribe | ||
| release Dispose | ||
| kind "subscription token" | ||
| } | ||
|
|
||
| // On window close the VM disposes (unsubscribes) its subscription, but a late | ||
| // queued callback still touches it. Using a subscription after Dispose is the | ||
| // generic use-after-release (OWN002), tagged with the resource kind. | ||
| fn CloseHandler(bus: int) { | ||
| let sub = acquire Subscription(bus); | ||
| release sub; // unsubscribed / disposed on close | ||
| use sub; // a late callback still touches it -> OWN002 | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| OWN002 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # WPF subscription used after Dispose | ||
|
|
||
| **Pattern:** a ViewModel unsubscribes / disposes its subscription on close, but a | ||
| callback that was already queued on the dispatcher still runs and touches the | ||
| disposed, subscription-backed state. In real code this is an | ||
| `ObjectDisposedException` or a read of torn state — the use-after-dispose cousin | ||
| of the zombie-ViewModel leak. | ||
|
|
||
| **What the checker says:** using a resource after its `release` (Dispose) is the | ||
| generic **OWN002** (use after release), carrying the resource-kind tag: | ||
|
|
||
| ```text | ||
| $ python -m ownlang check corpus/wpf/handler-use-after-dispose/case.own | ||
| case.own:16:9: error: [OWN002] use 'sub' after it was released | ||
| [resource: subscription token] | ||
| 16 | use sub; | ||
| ^ | ||
| ``` | ||
|
|
||
| **Honesty / scope.** `case.own` is a *hand reduction* of the C# pattern, not | ||
| direct C# extractor output (the C# extractor in P-001 is narrow — event | ||
| subscriptions only). It shows the ownership | ||
| *logic* maps onto the real bug; it does not model the dispatcher queue or | ||
| exception flow. `before.cs` / `after.cs` are representative, not a verbatim copy | ||
| of one PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // FIXED. The subscription is kept as a disposable token and released when the | ||
| // VM is disposed (on window close), so the App-lived bus no longer holds the | ||
| // Window-lived VM: the VM drops back to its intended Window lifetime and is | ||
| // collectable. (In OwnLang terms this is the slice-#1 acquire/release token | ||
| // pattern; the region check then sees a release path and stays quiet.) | ||
| public sealed class CustomerViewModel : IDisposable | ||
| { | ||
| private readonly IDisposable _customerChanged; | ||
|
|
||
| public CustomerViewModel(IEventBus appBus) | ||
| { | ||
| _customerChanged = appBus.Subscribe<CustomerChanged>(OnCustomerChanged); | ||
| } | ||
|
|
||
| private void OnCustomerChanged(CustomerChanged e) { /* ... */ } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| _customerChanged.Dispose(); // release path -> VM no longer promoted | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // BUGGY (representative WPF pattern, hand-reduced into case.own). | ||
| // | ||
| // A Window-scoped ViewModel subscribes itself to an App-scoped (singleton) event | ||
| // bus with a strong handler and keeps no unsubscribe token. The bus is reachable | ||
| // from an App-lifetime GC root, and through the strong delegate so is the VM: | ||
| // the VM is *promoted* to App lifetime. Close the window all you want -- the VM | ||
| // lives until the process exits. The lifetime mismatch (VM expected Window, | ||
| // actually App) is the leak. | ||
| public sealed class CustomerViewModel | ||
| { | ||
| public CustomerViewModel(IEventBus appBus) // appBus: App lifetime (singleton) | ||
| { | ||
| // strong subscription, no token kept -> VM promoted to App lifetime | ||
| appBus.CustomerChanged += OnCustomerChanged; | ||
| } | ||
|
|
||
| private void OnCustomerChanged(object? sender, EventArgs e) { /* ... */ } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| module WpfRegionEscape | ||
|
|
||
| // Lifetime regions: a Window-lived ViewModel must not outlive its window, and | ||
| // the App-lived event bus outlives everything. | ||
| lifetime App; | ||
| lifetime Window < App; | ||
| lifetime ViewModel < Window; | ||
|
|
||
| // The ViewModel (ViewModel-lived) strongly subscribes itself to the App-lived | ||
| // bus. Because App strictly outlives ViewModel, the subscription promotes the | ||
| // VM to App lifetime -> it can never die while the app runs => OWN014. This is | ||
| // the region-escape theorem: the *ordering* is what makes it a leak (subscribing | ||
| // to a same/shorter-lived source would be fine). | ||
| fn CustomerViewModel(bus: EventBus lifetime App) lifetime ViewModel { | ||
| subscribe self to bus; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| OWN014 |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.