Skip to content

fix(extractor): WPF view-owns-VM exemption via sibling XAML (mined VideoSource FPs)#82

Merged
PhysShell merged 3 commits into
mainfrom
claude/fp-view-owns-vm
Jun 22, 2026
Merged

fix(extractor): WPF view-owns-VM exemption via sibling XAML (mined VideoSource FPs)#82
PhysShell merged 3 commits into
mainfrom
claude/fp-view-owns-vm

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Mined from NickeManarin/ScreenToGif (continued)

Closes the last false positives from the ScreenToGif (WPF) mining run, plus a small correctness fix to the App exemption that landed in #81.

1. View-owns-its-view-model — the 4 VideoSource OWN001 FPs

VideoSource.xaml.cs subscribes to four events on its own view-model (_viewModel.ShowErrorRequested += …, etc.), and the detector warned "injected dependency … may keep VideoSource alive". But the view constructs its VM in its own XAML:

<c:ExWindow.DataContext><v:VideoSourceViewModel/></c:ExWindow.DataContext>

So the view owns the VM — the view↔VM cycle is GC-collectable and the subscriptions aren't leaks.

The extractor parses only .cs, where this ownership is invisible (_viewModel = DataContext as VideoSourceViewModel). So it now reads the sibling .xaml: when a Foo.xaml.cs's Foo.xaml inline-constructs its DataContext (<X.DataContext><VM/> — a type instantiation, not a Binding/resource reference), a field assigned from this.DataContext is folded into the self-owned set, exactly like a new'd field. This is sound — XAML construction is provable ownership, with none of the aliasing hole that sank the ProcessHelper attempt: a <Binding/>/<StaticResource/> DataContext (externally supplied) is explicitly not treated as owned.

2. Scope the App OWN014 exemption to non-timers (CodeRabbit follow-up to #81)

#81's source == "static" && clsIsApp guard also swallowed timer findings in App, because a timer is forced to source = "static" a few lines up — and a never-stopped timer in App is still a real leak. Now gated on !isTimer.

Regression guards (wpf-extractor)

  • ViewOwnsVmSample.xaml(.cs) — the view owns its XAML-declared VM → must stay silent.
  • InjectedDcViewSample.xaml(.cs) — a bound (<Binding/>) DataContext is not owned → the subscription must still warn, proving the gate keys off proven construction, not every DataContext as T.

Mining scorecard (ScreenToGif, 8 actionable verdicts)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • Tests

    • Expanded CI WPF leak-extraction coverage with new ViewOwnsVmSample and InjectedDcViewSample inputs.
    • Added/strengthened the P-004 MVVM ownership assertion: CI now fails if ViewOwnsVmSample is flagged as leaking, and requires an OWN001 warning for injected DataContext with unknown lifetime.
  • Improvements

    • Enhanced WPF MVVM leak detection by recognizing views that own/construct their DataContext based on sibling XAML heuristics.
    • Adjusted WPF App static-source suppression to apply only to non-timer events.

…r-scope fix

Two subscription-detector precision fixes from the ScreenToGif mining run.

1. View-owns-its-view-model (the 4 VideoSource OWN001 FPs). A WPF view that
   CONSTRUCTS its view-model in its own XAML (`<X.DataContext><VM/></X.DataContext>`)
   OWNS it — the view<->VM cycle is GC-collectable, so subscribing to the VM's events
   is not a leak. The extractor parses only `.cs`, where this is invisible
   (`_vm = DataContext as VM`), so it now reads the sibling `.xaml`: when the view's
   own XAML inline-constructs its DataContext (a type instantiation, not a
   Binding/resource reference), a field assigned from `this.DataContext` is folded into
   the self-owned set, exactly like a `new`'d field. Sound — XAML construction is
   provable ownership, with no aliasing hole. Mined from ScreenToGif's VideoSource.

2. Scope the App OWN014 exemption to non-timers (CodeRabbit, follow-up to #81). A
   timer is forced to source "static", so the merged `source == "static" && clsIsApp`
   guard also swallowed timer findings in `App`; a never-stopped timer there is still a
   real leak. Gated on `!isTimer`.

Regression guards (wpf-extractor): ViewOwnsVmSample.xaml(.cs) — the view owns its
XAML-declared VM, must stay silent; InjectedDcViewSample.xaml(.cs) — a BOUND
(`<Binding/>`) DataContext is not owned, so its subscription must still warn,
proving the gate keys off proven construction, not every `DataContext as T`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a0339434-5168-41b8-92a8-d0f33c867235

📥 Commits

Reviewing files that changed from the base of the PR and between 72438b9 and bed4868.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs

📝 Walkthrough

Walkthrough

The PR adds XAML-sibling-parsing to the Roslyn extractor so it can detect when a WPF view constructs its own DataContext inline in XAML. Views that match are added to a viewsOwningDataContext set, and fields assigned from this.DataContext in those views are added to selfOwned, suppressing OWN001 leak warnings. The App static-source suppression is also narrowed to non-timer events. Two new sample pairs (one positive, one negative control) and corresponding CI assertions are added to validate the new P-004 MVVM ownership scenario.

Changes

WPF MVVM DataContext Ownership Detection

Layer / File(s) Summary
Positive and negative control samples
frontend/roslyn/samples/ViewOwnsVmSample.xaml, frontend/roslyn/samples/ViewOwnsVmSample.xaml.cs, frontend/roslyn/samples/InjectedDcViewSample.xaml, frontend/roslyn/samples/InjectedDcViewSample.xaml.cs
ViewOwnsVmSample sets DataContext to a new OwnedVm inline in XAML and reads it in code-behind (expected SILENT); InjectedDcViewSample binds DataContext from an injected source and casts it in code-behind (expected OWN001).
Extractor XAML heuristics, ownership set, and self-owned exemption
frontend/roslyn/OwnSharp.Extractor/Program.cs
Adds System.Xml.Linq; introduces ReadsDataContext and XamlDeclaresOwnedDataContext helpers; builds viewsOwningDataContext by reading sibling .xaml files; extends selfOwned exemption for fields assigned from this.DataContext in owning views; narrows the App static-source suppression to !isTimer.
CI inputs and P-004 assertions
.github/workflows/ci.yml
Adds both new .xaml.cs sample files to the Roslyn extraction inputs; adds assertions that ViewOwnsVmSample is absent from OWN001 findings and InjectedDcViewSample produces the expected injected-source OWN001 warning.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PhysShell/Own.NET#28: Both PRs extend the OWN001 event-subscription leak pipeline; this PR adds XAML-based DataContext ownership detection and new P-004 sample assertions, while the retrieved PR addresses injected/lambda handler severity logic and OWN001 WARN outcomes.
  • PhysShell/Own.NET#81: Both PRs modify the WPF App static-event escape suppression logic in Program.cs; this PR further narrows that suppression to non-timer events (!isTimer).

Poem

🐇 Hop hop, the XAML files speak,
"This DataContext is mine to keep!"
The extractor reads the sibling's claim,
No leak warning — all the same.
Injected VMs still bear their mark,
While owned ones skip the OWN001 bark! 🌿

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: implementing a WPF view-owns-VM exemption via sibling XAML parsing to fix false positives. It is specific, concise, and directly related to the primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fp-view-owns-vm

Comment @coderabbitai help to get the list of available commands and usage tips.


public void OnLoaded()
{
_vm.Changed += OnChanged; // unowned source -> possible leak -> warns

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cb2bcc3012

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// unrecognised shape yields false (no exemption), never a wrongly-suppressed leak.
static bool XamlDeclaresOwnedDataContext(string xaml)
{
foreach (Match m in Regex.Matches(xaml, @"\.DataContext\s*>\s*<\s*(?:[\w]+:)?([\w.]+)"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict DataContext ownership to the view itself

This match treats any property-element *.DataContext in the sibling XAML as proof that the code-behind view owns this.DataContext. In a common WPF layout where the root view has an injected/shared DataContext but a nested control declares its own <Grid.DataContext><local:ChildVm/></Grid.DataContext>, this returns true for the whole .xaml.cs file; the later ReadsDataContext path then adds fields assigned from this.DataContext to selfOwned, so subscriptions to the external VM are silently dropped instead of producing OWN001. The check needs to verify the constructed DataContext belongs to the XAML root/class being reviewed, not just any child element in the file.

Useful? React with 👍 / 👎.

…nt (Codex P2)

XamlDeclaresOwnedDataContext matched ANY `*.DataContext` property-element in the
sibling XAML. But the code-behind's `this.DataContext` is the ROOT element's, so a
nested `<Grid.DataContext><ChildVm/>` (a different element's DataContext) would wrongly
mark the whole `.xaml.cs` as owning — silently dropping a real OWN001 on the root's
injected/bound VM. Now: strip comments / processing-instructions, find the root element
tag, and match only `<Root.DataContext>` with a constructed (type-instantiation) child.

The InjectedDcView control XAML gains exactly this shape — a BOUND root DataContext
plus a nested `<Grid.DataContext><local:NestedChildVm/>` — so the existing "must warn"
assertion now also guards the root-restriction (without it the nested construction
would make the view look owned and the subscription would be wrongly suppressed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
.github/workflows/ci.yml (1)

307-308: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Bind the injected-wording assertion to InjectedDcViewSample explicitly.

Current checks assert warning presence for InjectedDcViewSample, but injected-wording is validated globally and could be satisfied by another finding. Add a file-scoped wording assertion to prevent this scenario from silently regressing.

Suggested CI assertion
           echo "$out" | grep -qE "InjectedDcViewSample\.xaml\.cs:[0-9]+: warning: \[OWN001\]" \
             || { echo "FAIL: a bound (unowned) DataContext subscription must still warn"; exit 1; }
+          echo "$out" | grep -qE "InjectedDcViewSample\.xaml\.cs:[0-9]+: warning: \[OWN001\].*injected dependency whose lifetime is unknown" \
+            || { echo "FAIL: expected injected-source wording for InjectedDcViewSample"; exit 1; }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 307 - 308, The current grep assertion
for InjectedDcViewSample.xaml.cs with OWN001 warning is too broad and could pass
if the warning appears in any file. Add an additional explicit file-scoped
wording assertion that strictly validates the injected-wording is present in the
InjectedDcViewSample output specifically, ensuring the warning is tied directly
to that file and preventing silent regression if the warning moves to another
source.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/roslyn/OwnSharp.Extractor/Program.cs`:
- Around line 253-263: The XamlDeclaresOwnedDataContext method uses an overly
broad regex pattern that matches any .DataContext property-element in the XAML
file, incorrectly classifying nested template DataContexts or non-instantiation
objects like x:Static as owned DataContexts. Narrow the detection to only the
view's actual root DataContext object element by refining the regex pattern or
preferably by using structural XAML parsing instead of a broad pattern-matching
approach. This will prevent the selfOwned path from incorrectly suppressing
OWN001 violations for these edge cases.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 307-308: The current grep assertion for
InjectedDcViewSample.xaml.cs with OWN001 warning is too broad and could pass if
the warning appears in any file. Add an additional explicit file-scoped wording
assertion that strictly validates the injected-wording is present in the
InjectedDcViewSample output specifically, ensuring the warning is tied directly
to that file and preventing silent regression if the warning moves to another
source.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1f2100df-4af0-4b20-98f3-2f0dd0b1e10e

📥 Commits

Reviewing files that changed from the base of the PR and between fffa098 and cb2bcc3.

📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/InjectedDcViewSample.xaml
  • frontend/roslyn/samples/InjectedDcViewSample.xaml.cs
  • frontend/roslyn/samples/ViewOwnsVmSample.xaml
  • frontend/roslyn/samples/ViewOwnsVmSample.xaml.cs

Comment thread frontend/roslyn/OwnSharp.Extractor/Program.cs Outdated
…p (CodeRabbit)

Replace the textual regex in XamlDeclaresOwnedDataContext with structural XML parsing
(XDocument), per CodeRabbit's recommendation. Strictly more correct: it inspects only
the ROOT element's own `<Root.DataContext>` (a direct child, in the root's namespace)
and excludes the entire `x:` language namespace — so x:Static / x:Null / x:Reference
(external or shared values the view does NOT own) no longer count as construction,
closing a residual false-negative the regex left open. A malformed `.xaml` parses to
false (conservative). Drops the now-unused Regex import.

Also tighten the InjectedDcView CI assertion to require the OWN001 warning AND the
injected-source wording on that file's line specifically (CodeRabbit nitpick), so the
negative control cannot be satisfied by an unrelated finding.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
@PhysShell PhysShell merged commit eba8c9a into main Jun 22, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants