Skip to content

feat(extractor): silence multi-arg self-WhenAnyValue (precision dig + CI sample)#47

Merged
PhysShell merged 2 commits into
mainfrom
claude/self-whenany-precision
Jun 19, 2026
Merged

feat(extractor): silence multi-arg self-WhenAnyValue (precision dig + CI sample)#47
PhysShell merged 2 commits into
mainfrom
claude/self-whenany-precision

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Digs into the "residual self-WhenAnyValue FP" caveat from the WalletWasabi oracle run, then fixes the one genuine FP it found.

The dig (docs/notes/self-whenany-precision.md)

The own-only WhenAnyValue findings looked like they might include self-cycle FPs. Checking the real WalletWasabi source flips that: BitcoinTabSettingsViewModel.Settings is a constructor-injected, long-lived ApplicationSettings, so this.WhenAnyValue(x => x.Settings.Foo).Subscribe (result ignored) attaches a handler to the long-lived object and keeps the transient VM alive — a real leak. So most own-only nested findings are real leaks, and the classifier's conservatism (not silencing nested paths, which can run through an injected object) is correct. The differentiation is stronger than the caveat implied.

The one genuine residual FP is narrow: multi-arg, single-hop ownthis.WhenAnyValue(x => x.A, x => x.B).Subscribe, where both selectors are the component's own properties. That observes only this → a collectable self-cycle, but the classifier missed it solely because it required Arguments.Count == 1.

The fix

  • Program.cs: generalise IsSelfRootedWhenAny from one single-hop self selector to one-or-more (IsSelfMemberSelector + a loop; no System.Linq, which isn't imported). A nested path (p => p.A.B) and a result-combiner overload (…, (a, b) => …) stay flagged (conservative — they can observe an injected object).
  • Add frontend/roslyn/samples/WhenAnyValueViewModel.cs and wire it into the wpf-extractor CI job — the first WhenAnyValue sample, so this classifier finally gets a CI regression pin: single-arg and multi-arg self silenced; a nested injected path and an external combinator flagged (exactly 2 OWN001).

Validation

The bridge half + the assertion logic are validated locally (the facts the extractor emits → 2 OWN001, the three self chains silent, x => x.B never surfaces). The C# classifier change has no local .NET SDK, so it is validated by the wpf-extractor job — which is exactly why the sample lands with it. ci.yml parses.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • Refactor

    • Enhanced pattern recognition to handle multiple selector arguments in reactive subscription chains
  • Documentation

    • Added analysis documentation covering subscription pattern detection precision and investigation findings
  • Tests

    • Expanded test suite with new code samples to improve coverage of reactive subscription scenarios

claude added 2 commits June 19, 2026 17:53
…sion)

Investigate the "residual self-WhenAnyValue FP" caveat on the WalletWasabi oracle
run by checking the real target source.

Finding: most own-only nested findings (x => x.Settings.Foo) are REAL leaks —
Settings is a constructor-injected, long-lived ApplicationSettings that keeps the
transient view-model alive, result ignored — so the classifier's conservatism is
correct and the differentiation is stronger than the caveat implied. The genuine
residual FP is the narrow multi-arg single-hop own case (x => x.A, x => x.B),
which the classifier misses only because it requires Arguments.Count == 1.

Records the exact fix (generalise IsSelfRootedWhenAny to >=1 single-hop self
selectors, no System.Linq) and the coverage gap: no WhenAnyValue sample exists,
so the classifier is CI-untested and the fix must land with a wpf-extractor
sample (no local dotnet to validate).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED
Implements the fix from docs/notes/self-whenany-precision.md. The ignored-
.Subscribe() source classifier silenced only a single-arg single-hop self
selector (this.WhenAnyValue(p => p.Member)); a multi-arg self chain
(this.WhenAnyValue(x => x.A, x => x.B)) over the component's OWN single-hop
properties is the SAME collectable self-cycle but was wrongly flagged — a residual
false positive confirmed on the WalletWasabi oracle run.

- Program.cs: generalise IsSelfRootedWhenAny from one single-hop self selector to
  one-or-more (IsSelfMemberSelector + a loop; no System.Linq, which isn't
  imported). A nested path (p => p.A.B — can observe an injected object) and a
  result-combiner overload (..., (a, b) => ...) stay flagged.
- Add frontend/roslyn/samples/WhenAnyValueViewModel.cs and wire it into the
  wpf-extractor CI job. It is the first WhenAnyValue sample, so the classifier
  finally gets a CI regression pin: single-arg AND multi-arg self silenced; a
  nested injected path and an external combinator flagged (exactly 2 OWN001).

Bridge-side validated locally (the facts the extractor emits -> 2 OWN001, the
three self chains silent, "x => x.B" never surfaces); the C# classifier change is
validated by the wpf-extractor job (no local dotnet). ci.yml parses.

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 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b4381c0b-4565-4281-8409-37cd258959a7

📥 Commits

Reviewing files that changed from the base of the PR and between 86dce9e and aa9187e.

📒 Files selected for processing (4)
  • .github/workflows/ci.yml
  • docs/notes/self-whenany-precision.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/WhenAnyValueViewModel.cs

📝 Walkthrough

Walkthrough

Extends IsSelfRootedWhenAny in the Roslyn extractor to handle multi-argument WhenAnyValue calls by introducing an IsSelfMemberSelector helper and iterating all selector arguments. Adds a WhenAnyValueViewModel.cs sample with annotated SILENCED/FLAGGED cases, wires it into CI, and adds a design note documenting the precision investigation.

Changes

WhenAnyValue Self-Cycle Precision

Layer / File(s) Summary
Investigation note and proposed classifier design
docs/notes/self-whenany-precision.md
Adds a full note covering the SARIF run results, the argument that nested selectors represent real leaks via injected dependencies, the narrow false-positive shape for multi-arg single-hop own selectors, the proposed IsSelfRootedWhenAny broadening, and the CI coverage gap that motivated adding the new sample.
IsSelfMemberSelector helper and multi-arg IsSelfRootedWhenAny
frontend/roslyn/OwnSharp.Extractor/Program.cs
Introduces IsSelfMemberSelector to validate a single-hop self-member lambda (p => p.Member), then replaces the inline single-selector check with a loop requiring every selector argument to satisfy the helper; zero-selector calls and non-WhenAnyValue heads still return false.
WhenAnyValueViewModel sample and CI assertions
frontend/roslyn/samples/WhenAnyValueViewModel.cs, .github/workflows/ci.yml
Adds the sample class with A, B, injected Svc, and a Feed field, annotating each WhenAnyValue(...).Subscribe(...) chain as SILENCED or FLAGGED. Wires the file into the extractor invocation and adds CI checks asserting two OWN001 findings (nested path and CombineLatest), silencing of the multi-arg self chain (x => x.B), and an exact finding count for the file.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PhysShell/Own.NET#40: Both PRs modify frontend/roslyn/OwnSharp.Extractor/Program.cs to refine the "self-rooted this.WhenAnyValue(...)" classification, with that PR establishing the single-arg shape and this PR extending it to multi-arg selectors.

Poem

🐇 Hop, hop through the lambda chain,
One selector, two — now both are plain!
p => p.Member checked with care,
No nested paths shall slip through there.
The CI counts say "two, no more" —
A tidy fix worth leaping for! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 describes the main changes: silencing multi-arg self-WhenAnyValue detection through a precision investigation and CI sample addition.
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/self-whenany-precision

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

@PhysShell
PhysShell merged commit a24fecf into main Jun 19, 2026
17 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.

2 participants