Skip to content

fix(extractor): resolve-aware field disposability (mined ImageSharp FP #1/4)#83

Merged
PhysShell merged 2 commits into
mainfrom
claude/fp-resolve-aware-disposable
Jun 22, 2026
Merged

fix(extractor): resolve-aware field disposability (mined ImageSharp FP #1/4)#83
PhysShell merged 2 commits into
mainfrom
claude/fp-resolve-aware-disposable

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Mined from SixLabors/ImageSharp — fix 1 of 4

Mining ImageSharp (a pool/IDisposable-heavy library) surfaced 4 distinct detector FPs. This is the first and highest-value fix.

The bug

The field-disposable leak detector classified a field's type with a pure name heuristic (EndsWith Stream/Reader/Writer/Subscription). So any in-project type whose name ends that way was flagged even when it is not IDisposable:

flagged field type actually IDisposable?
Vp8Encoder.bitWriter Vp8BitWriter : BitWriterBase ❌ no
Vp8LEncoder.bitWriter Vp8LBitWriter : BitWriterBase ❌ no
ArithmeticScanDecoder.scanBuffer JpegBitReader (a struct) ❌ no
HuffmanScanDecoder.scanBuffer JpegBitReader (a struct) ❌ no

BitWriterBase has no interface list and JpegBitReader is a struct — none implement IDisposable. Four false positives.

The fix

IsOwnedDisposableType now asks the resolved type's real IDisposable/IAsyncDisposable interface, and only falls back to the name heuristic when the type does not resolve (an unreferenced external assembly — WPF/DevExpress on a Linux runner, which is the whole reason that heuristic exists). This generalises the #79 PipeReader/PipeWriter exclusion — any resolved non-IDisposable type is now excluded, no curated list needed.

Regression guard

ResolvedDisposableSample.cs — a resolved non-IDisposable …Writer field and a non-IDisposable …Reader struct stay silent; resolved IDisposable controls (MemoryStream, CancellationTokenSource) still warn.

Coming next (this mining run)

  1. null-conditional field?.Dispose() not recognised (~9 FPs)
  2. pooled-buffer field returned in Dispose not seen (3 FPs)
  3. OWN014 on a static class with static handlers (2 FPs)

🤖 Generated with Claude Code

https://claude.ai/code/session_01Rg8kSk1YT14x7A1vo5zgED


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved disposability detection by using resolved type information, reducing false positives for types that merely resemble disposable “Reader/Writer/Stream” patterns but don’t implement IDisposable/IAsyncDisposable.
    • Updated behavior to properly flag owned-disposable fields when they resolve to real disposable types, while keeping “dispose-optional” cases silent.
  • Tests / CI
    • Expanded verification coverage to ensure the analyzer produces the expected warnings or remains silent for the new scenarios.

…posable (mined ImageSharp)

The field-disposable leak detector classified a field's type with a pure NAME heuristic
(EndsWith Stream/Reader/Writer/Subscription), so any in-project type whose name happens to
end that way was flagged even when it is NOT IDisposable. Mining SixLabors/ImageSharp
surfaced the FPs: undisposed `Vp8BitWriter`/`Vp8LBitWriter` (`: BitWriterBase`) and
`JpegBitReader` (a struct) fields — none of them IDisposable.

IsOwnedDisposableType now asks the RESOLVED type's real IDisposable/IAsyncDisposable
interface; it falls back to the name heuristic only when the type does NOT resolve (an
unreferenced external assembly — WPF/DevExpress on a Linux runner — which is the whole
reason that heuristic exists). This generalises the #79 PipeReader/PipeWriter exclusion:
any resolved non-IDisposable type is now excluded, no curated list needed.

Regression sample ResolvedDisposableSample.cs: a resolved non-IDisposable `…Writer` field
and a non-IDisposable `…Reader` struct stay silent; resolved IDisposable controls
(MemoryStream, CancellationTokenSource) still warn.

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: 8fc57d61-d842-40bf-a414-41a9000092b6

📥 Commits

Reviewing files that changed from the base of the PR and between d2c0f92 and ec4ce5c.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/ResolvedDisposableSample.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

Replaces the name-based IsDisposableType heuristic in the WPF003 owned-field detector with a new IsOwnedDisposableType function that resolves types via SemanticModel and checks for actual IDisposable/IAsyncDisposable interface implementation, falling back to the name heuristic only when resolution fails. A new sample file and corresponding CI assertions validate both the silent and warning cases.

Changes

Resolve-aware IDisposable detection for WPF003

Layer / File(s) Summary
Semantic disposability helper and WPF003 integration
frontend/roslyn/OwnSharp.Extractor/Program.cs
Introduces IsOwnedDisposableType(TypeSyntax, SemanticModel) that checks resolved type symbols for IDisposable/IAsyncDisposable, falling back to the prior name heuristic when the type is unresolved. The WPF003 owned-field gate is updated to call this new function instead of IsDisposableType.
Sample test cases for resolved and optional disposability
frontend/roslyn/samples/ResolvedDisposableSample.cs
Adds five control types: FancyBitWriter and TokenReader (non-IDisposable despite naming patterns), EncoderWithNonDisposableWriter (stores both; expected silent), HolderWithRealDisposable with MemoryStream and CancellationTokenSource fields (expected to warn), and HolderWithDisposeOptional with Task and DataTable fields (expected silent due to dispose-optional semantics).
CI input wiring and verification assertions
.github/workflows/ci.yml
Adds ResolvedDisposableSample.cs to the extractor's C# input list and inserts shell-based assertions: EncoderWithNonDisposableWriter must not appear in output, HolderWithRealDisposable must appear with an OWN001 warning, and HolderWithDisposeOptional must remain absent.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PhysShell/Own.NET#20: Modifies the same WPF003 owned-disposable field detector in Program.cs, adjusting detection behavior (skipping static fields) in the same code region touched by this PR.
  • PhysShell/Own.NET#79: Also modifies Program.cs to refine Reader/Writer/Stream disposability heuristics for leak detectors and adds CI sample-driven assertions, directly paralleling the approach of this PR.

Poem

🐇 Hoppity-hop through the syntax tree,
No more false alarms for FancyBitWriter free!
SemanticModel whispers, "Does it really dispose?"
MemoryStream confesses — a leak, and it shows.
The rabbit resolves what mere names cannot see. ✨

🚥 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 reflects the main change: implementing resolve-aware field disposability detection to fix false positives discovered in ImageSharp, which is the primary objective of this PR.
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-resolve-aware-disposable

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

// Control: resolved IDisposable fields the class new's but never disposes -> must WARN.
public sealed class HolderWithRealDisposable
{
private readonly MemoryStream stream = new(); // MemoryStream IS IDisposable
public sealed class HolderWithRealDisposable
{
private readonly MemoryStream stream = new(); // MemoryStream IS IDisposable
private readonly CancellationTokenSource cts = new(); // CancellationTokenSource IS IDisposable

@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: d2c0f925d8

ℹ️ 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".

var sym = model.GetTypeInfo(type).Type;
if (sym is null or IErrorTypeSymbol)
return IsDisposableType(type.ToString());
return ImplementsDisposable(sym);

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 Keep optional-dispose exemptions for resolved fields

For fields whose type now resolves semantically, this flags every IDisposable implementation, including types the extractor already treats as optional-dispose (Task, ValueTask, DataTable, DataSet, DataView in IsDisposeOptional). A class with private readonly DataTable table = new(); or private readonly Task task = new(...); was intentionally silent under the old syntactic IsDisposableType path but will now emit WPF003 because AllInterfaces contains IDisposable, reintroducing the Task/DataTable false positives documented just above IsDisposeOptional. Consider applying the same optional-dispose filter before returning true here.

Useful? React with 👍 / 👎.

@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

🤖 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 @.github/workflows/ci.yml:
- Around line 213-216: The grep assertion checking for HolderWithRealDisposable
only validates the class name is present, allowing the CI to pass even if the
diagnostic code or resource details regress. Strengthen the grep pattern in the
echo command to also match the specific diagnostic code [OWN001] and ideally the
resource field description (resource: disposable field) to ensure the complete
diagnostic output maintains the intended contract for detecting unresolved
IDisposable fields like MemoryStream or CancellationTokenSource.
🪄 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: ef0d9b93-e96e-420c-a250-0579244fbc53

📥 Commits

Reviewing files that changed from the base of the PR and between eba8c9a and d2c0f92.

📒 Files selected for processing (3)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/ResolvedDisposableSample.cs

Comment thread .github/workflows/ci.yml Outdated
… (Codex); tighten CI assertion (CodeRabbit)

Codex P2: the resolve-aware IsOwnedDisposableType flagged EVERY real IDisposable, so
dispose-optional types the flat name path skipped for free — Task / ValueTask /
DataTable / DataSet / DataView — would re-FP as undisposed fields. Reuse the existing
ImplementsIDisposable + IsDisposeOptional helpers (shared with the flow detector):
`ImplementsIDisposable(sym) && !IsDisposeOptional(sym)`. Dropped the duplicate
ImplementsDisposable/IsDisposableInterface helpers I had added.

Sample gains a HolderWithDisposeOptional control (a new'd, undisposed Task + DataTable)
that must stay silent. CI: strengthen the positive assertion to OWN001 + the
disposable-field resource on ResolvedDisposableSample (CodeRabbit), kept
severity-agnostic since the disposable-field leak renders as error, not warning.

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